├── .github └── workflows │ └── publish.yml ├── .gitignore ├── .gitmodules ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── build └── empty ├── docs ├── README.md ├── demo.gif ├── privacy-policy.md └── screenshot-comparison.png ├── installer ├── EnumUsersReg.nsh ├── install.nsi ├── licensetext.txt └── plugins │ ├── LockedList.dll │ ├── LockedList.txt │ └── LockedList64.dll └── src ├── COMUtils.cpp ├── COMUtils.h ├── ChainWindow.cpp ├── ChainWindow.h ├── CreateItemWindow.cpp ├── CreateItemWindow.h ├── DPI.cpp ├── DPI.h ├── ExecuteCommand.cpp ├── ExecuteCommand.h ├── FolderWindow.cpp ├── FolderWindow.h ├── GDIUtils.cpp ├── GDIUtils.h ├── GeomUtils.h ├── ItemWindow.cpp ├── ItemWindow.h ├── PreviewHandler.cpp ├── PreviewHandler.h ├── PreviewWindow.cpp ├── PreviewWindow.h ├── ProxyIcon.cpp ├── ProxyIcon.h ├── Settings.cpp ├── Settings.h ├── SettingsDialog.cpp ├── SettingsDialog.h ├── ShellUtils.cpp ├── ShellUtils.h ├── TextWindow.cpp ├── TextWindow.h ├── ThumbnailView.cpp ├── ThumbnailView.h ├── TrayWindow.cpp ├── TrayWindow.h ├── UIStrings.cpp ├── UIStrings.h ├── Update.cpp ├── Update.h ├── WinUtils.cpp ├── WinUtils.h ├── common.h ├── dialog.h ├── dialog.rc ├── main.cpp ├── main.h ├── res ├── ChromaFiler.exe.manifest ├── SegMDL2-subset.ttf ├── adwaita │ ├── COPYING │ └── right_side.cur ├── folder.ico ├── subset.txt └── text.ico ├── resource.h ├── resource.rc └── text ├── resource.rc └── textmain.cpp /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: ["main"] 4 | workflow_dispatch: 5 | gollum: 6 | release: 7 | 8 | concurrency: 9 | group: "publish" 10 | cancel-in-progress: false 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: vanjac/chroma-zone-publish@main 17 | with: 18 | token: ${{ secrets.ACTIONS_GITHUB_TOKEN }} 19 | wiki: docs 20 | download: true 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/* 2 | !/build/empty 3 | /.vscode/settings.json 4 | /.vs 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "installer/nsis-shortcut-properties"] 2 | path = installer/nsis-shortcut-properties 3 | url = https://github.com/safing/nsis-shortcut-properties 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "ChromaFiler", 9 | "type": "cppvsdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}\\build\\ChromaFiler.exe", 12 | "args": [], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}\\build", 15 | "environment": [], 16 | "console": "integratedTerminal" 17 | }, 18 | { 19 | "name": "Tray", 20 | "type": "cppvsdbg", 21 | "request": "launch", 22 | "program": "${workspaceFolder}\\build\\ChromaFiler.exe", 23 | "args": ["/tray"], 24 | "stopAtEntry": false, 25 | "cwd": "${workspaceFolder}\\build", 26 | "environment": [], 27 | "console": "integratedTerminal" 28 | }, 29 | { 30 | "name": "Text", 31 | "type": "cppvsdbg", 32 | "request": "launch", 33 | "program": "${workspaceFolder}\\build\\ChromaFiler.exe", 34 | "args": ["/text"], 35 | "stopAtEntry": false, 36 | "cwd": "${workspaceFolder}\\build", 37 | "environment": [], 38 | "console": "integratedTerminal" 39 | }, 40 | { 41 | "name": "Local Server", 42 | "type": "cppvsdbg", 43 | "request": "launch", 44 | "program": "${workspaceFolder}\\build\\ChromaFiler.exe", 45 | "args": ["-embedding"], 46 | "stopAtEntry": false, 47 | "cwd": "${workspaceFolder}\\build", 48 | "environment": [], 49 | "console": "integratedTerminal" 50 | }, 51 | { 52 | "name": "Explorer", 53 | "type": "cppvsdbg", 54 | "request": "launch", 55 | "program": "explorer.exe", 56 | "args": [], 57 | "stopAtEntry": false, 58 | "cwd": "${workspaceFolder}\\build", 59 | "environment": [], 60 | "console": "integratedTerminal" 61 | }, 62 | 63 | ] 64 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build resources", 6 | "command": "rc.exe", 7 | "args": [ 8 | "/D", "_UNICODE", 9 | "/D", "UNICODE", 10 | "/n", 11 | "/fo", "${workspaceFolder}\\build\\resource.res", 12 | "${workspaceFolder}\\src\\resource.rc", 13 | ], 14 | "group": { 15 | "kind": "build", 16 | }, 17 | }, 18 | { 19 | "label": "ChromaText resources", 20 | "command": "rc.exe", 21 | "args": [ 22 | "/fo", "${workspaceFolder}\\build\\text.res", 23 | "${workspaceFolder}\\src\\text\\resource.rc", 24 | ], 25 | "group": { 26 | "kind": "build", 27 | }, 28 | }, 29 | { 30 | "type": "cppbuild", 31 | "label": "ChromaText", 32 | "dependsOn": ["ChromaText resources"], 33 | "command": "cl.exe", 34 | "args": [ 35 | "/nologo", 36 | "/GL", "/O2", "/MP", "/W4", 37 | "/MT", "/GS-", 38 | "/D", "UNICODE", "/D", "_UNICODE", 39 | "/Fe:", "${workspaceFolder}\\build\\ChromaText.exe", 40 | "/I", "${workspaceFolder}\\src", 41 | "${workspaceFolder}\\src\\text\\textmain.cpp", 42 | "${workspaceFolder}\\build\\text.res", 43 | "Kernel32.lib", "Shlwapi.lib", 44 | "/link", 45 | "/incremental:no", "/manifest:no", "/subsystem:windows", 46 | "/NODEFAULTLIB", "/ENTRY:wWinMainCRTStartup", 47 | "/EMITPOGOPHASEINFO", "/SAFESEH:NO" 48 | ], 49 | "options": { 50 | "cwd": "${workspaceFolder}\\build" 51 | }, 52 | "problemMatcher": [ 53 | "$msCompile" 54 | ], 55 | "group": { 56 | "kind": "build", 57 | }, 58 | }, 59 | { 60 | "type": "cppbuild", 61 | "label": "Debug build", 62 | "dependsOn": ["Build resources"], 63 | "command": "cl.exe", 64 | "args": [ 65 | "/Zi", // debug only 66 | "/EHsc", // debug only 67 | "/MTd", // debug only 68 | "/D", "CHROMAFILER_DEBUG", // debug only 69 | // "/D", "CHROMAFILER_MEMLEAKS", // debug only 70 | "/nologo", 71 | "/MP", // https://randomascii.wordpress.com/2014/03/22/make-vc-compiles-fast-through-parallel-compilation/ 72 | "/W4", 73 | "/Fe:", "${workspaceFolder}\\build\\ChromaFiler.exe", 74 | "/I", "${workspaceFolder}\\src", 75 | "${workspaceFolder}\\src\\*.cpp", 76 | "${workspaceFolder}\\build\\resource.res", 77 | "/link", 78 | "/incremental:no", 79 | "/manifest:no", 80 | "/subsystem:console", // debug only 81 | ], 82 | "options": { 83 | "cwd": "${workspaceFolder}\\build" 84 | }, 85 | "problemMatcher": [ 86 | "$msCompile" 87 | ], 88 | "group": { 89 | "kind": "build", 90 | "isDefault": true 91 | }, 92 | }, 93 | { 94 | "type": "cppbuild", 95 | "label": "Release build", 96 | "dependsOn": ["Build resources", "ChromaText"], 97 | "command": "cl.exe", 98 | "args": [ 99 | "/GL", // release only 100 | "/O2", // release only 101 | "/D", "_HAS_EXCEPTIONS=0", // release only 102 | "/nologo", 103 | "/MP", 104 | "/W4", 105 | "/Fe:", "${workspaceFolder}\\build\\ChromaFiler.exe", 106 | "/I", "${workspaceFolder}\\src", 107 | "${workspaceFolder}\\src\\*.cpp", 108 | "${workspaceFolder}\\build\\resource.res", 109 | "/link", 110 | "/incremental:no", 111 | "/manifest:no", 112 | ], 113 | "options": { 114 | "cwd": "${workspaceFolder}\\build" 115 | }, 116 | "problemMatcher": [ 117 | "$msCompile" 118 | ], 119 | "group": { 120 | "kind": "build", 121 | }, 122 | } 123 | ] 124 | } -------------------------------------------------------------------------------- /build/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanjac/chromafiler/fde6bd6a8a80a749c8565822f581500c927494ab/build/empty -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # ChromaFiler 2 | 3 | ChromaFiler is a file manager utility with a unique interface, borrowing ideas from [Spatial file managers](https://en.wikipedia.org/wiki/Spatial_file_manager) and [Miller Column browsers](https://en.wikipedia.org/wiki/Miller_columns) and taking inspiration from the [classic Mac OS](https://en.wikipedia.org/wiki/Classic_Mac_OS) Finder. 4 | 5 | An animation demonstrating folder navigation using ChromaFiler 6 | 7 | It functions similar to a column-view browser, but each column can be broken off into its own window by dragging and dropping. You can use it as a popup menu docked to your taskbar for quickly locating a file, or as a complete replacement for Windows File Explorer (and Notepad). [More about the design](https://chroma.zone/chromafiler/docs/Spatial-Orientation.html). 8 | 9 | ChromaFiler works on Windows 7 through 11. 10 | 11 | Sequence of ChromaFiler windows for each version of Windows 12 | 13 | ## Download 14 | 15 | **Check the [Releases](https://github.com/vanjac/chromafiler/releases) page for the latest beta release.** See [installation instructions](https://chroma.zone/chromafiler/docs/Installation.html) for additional information. 16 | 17 | ChromaFiler is still in development, so be sure to turn on the [auto update check](https://chroma.zone/chromafiler/docs/Settings.html#updateabout) feature so you'll be notified of new releases. 18 | 19 | ## Tutorial 20 | 21 | See the [wiki](https://chroma.zone/chromafiler/docs/Tutorial.html) for an introduction to the app. 22 | 23 | ## Building 24 | 25 | Building requires MSVC and the Windows 10 SDK (install through the [Visual Studio Installer](https://visualstudio.microsoft.com/downloads/); I'm using VC++ 2017 and SDK 10.0.17763.0), as well as [Visual Studio Code](https://code.visualstudio.com/) for running build scripts. The installer is built using [NSIS](https://nsis.sourceforge.io/Main_Page). (You will need to [clone all submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules#_cloning_submodules).) 26 | 27 | VS Code must be launched from the "x64 Native Tools Command Prompt" (or equivalent for your platform) to access the correct MSVC build tools, which can be found by searching the Start menu. Type `code` in this prompt to launch VS Code. Open the ChromaFiler directory, then open `src/main.cpp` and press `Ctrl+Shift+B` to build the app in the Debug configuration. 28 | 29 | ## Suggested pairings 30 | 31 | - [Everything](https://www.voidtools.com/) by voidtools (recommend installing with folder context menus) 32 | - [Microsoft PowerToys](https://github.com/microsoft/PowerToys) (see notes on [preview handlers](https://chroma.zone/chromafiler/docs/Preview-Handlers.html)) 33 | 34 | ## Donate 35 | 36 | If you find ChromaFiler useful, please consider [donating](https://chroma.zone/donate) to support development. 37 | 38 | ## Contact me 39 | 40 | For questions / feedback / bug reports please [email me](https://chroma.zone/contact) 41 | -------------------------------------------------------------------------------- /docs/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanjac/chromafiler/fde6bd6a8a80a749c8565822f581500c927494ab/docs/demo.gif -------------------------------------------------------------------------------- /docs/privacy-policy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | ChromaFiler does not collect or share any personal information. The app may be used to access files on your device, but no information on these files will be collected or stored elsewhere. 4 | -------------------------------------------------------------------------------- /docs/screenshot-comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanjac/chromafiler/fde6bd6a8a80a749c8565822f581500c927494ab/docs/screenshot-comparison.png -------------------------------------------------------------------------------- /installer/EnumUsersReg.nsh: -------------------------------------------------------------------------------- 1 | # source: https://nsis.sourceforge.io/EnumUsersReg 2 | 3 | !ifndef ___EnumUsersReg___ 4 | !define ___EnumUsersReg___ 5 | 6 | !include "LogicLib.nsh" 7 | 8 | !define TOKEN_QUERY 0x0008 9 | !define TOKEN_ADJUST_PRIVILEGES 0x0020 10 | 11 | !define SE_RESTORE_NAME SeRestorePrivilege 12 | 13 | !define SE_PRIVILEGE_ENABLED 0x00000002 14 | 15 | !define HKEY_USERS 0x80000003 16 | 17 | !macro _EnumUsersReg_AdjustTokens 18 | 19 | StrCpy $R1 0 20 | 21 | System::Call "kernel32::GetCurrentProcess() i .R0" 22 | System::Call "advapi32::OpenProcessToken(i R0, i ${TOKEN_QUERY}|${TOKEN_ADJUST_PRIVILEGES}, \ 23 | *i R1R1) i .R0" 24 | 25 | ${If} $R0 != 0 26 | System::Call "advapi32::LookupPrivilegeValue(t n, t '${SE_RESTORE_NAME}', *l .R2) i .R0" 27 | 28 | ${If} $R0 != 0 29 | System::Call "*(i 1, l R2, i ${SE_PRIVILEGE_ENABLED}) i .R0" 30 | System::Call "advapi32::AdjustTokenPrivileges(i R1, i 0, i R0, i 0, i 0, i 0)" 31 | System::Free $R0 32 | ${EndIf} 33 | 34 | System::Call "kernel32::CloseHandle(i R1)" 35 | ${EndIf} 36 | 37 | !macroend 38 | 39 | !macro _EnumUsersReg_InvokeCallback CALLBACK SUBKEY 40 | 41 | Push $0 42 | Push $1 43 | Push $R0 44 | Push $R1 45 | Push $R2 46 | 47 | Push "${SUBKEY}" 48 | 49 | Call "${CALLBACK}" 50 | 51 | Pop $R2 52 | Pop $R1 53 | Pop $R0 54 | Pop $1 55 | Pop $0 56 | 57 | !macroend 58 | 59 | !macro _EnumUsersReg_Load FILE CALLBACK SUBKEY 60 | 61 | GetFullPathName /SHORT $R2 ${FILE} 62 | System::Call "advapi32::RegLoadKey(i ${HKEY_USERS}, t '${SUBKEY}', t R2) i .R2" 63 | 64 | ${If} $R2 == 0 65 | !insertmacro _EnumUsersReg_InvokeCallback "${CALLBACK}" "${SUBKEY}" 66 | System::Call "advapi32::RegUnLoadKey(i ${HKEY_USERS}, t '${SUBKEY}')" 67 | ${EndIf} 68 | 69 | !macroend 70 | 71 | !macro EnumUsersReg UN CALLBACK SUBKEY 72 | 73 | Push $0 74 | Push $1 75 | 76 | GetFunctionAddress $0 "${CALLBACK}" 77 | StrCpy $1 "${SUBKEY}" 78 | Call ${UN}_EnumUsersReg 79 | 80 | Pop $1 81 | Pop $0 82 | 83 | !macroend 84 | 85 | !define EnumUsersReg '!insertmacro EnumUsersReg ""' 86 | !define un.EnumUsersReg '!insertmacro EnumUsersReg "un."' 87 | 88 | !macro _EnumUsersReg UN 89 | Function ${UN}_EnumUsersReg 90 | 91 | Push $R0 92 | Push $R1 93 | Push $R2 94 | 95 | # enumerate logged on users 96 | 97 | StrCpy $R0 0 98 | ${Do} 99 | EnumRegKey $R1 HKU "" $R0 100 | ${If} $R1 != "" 101 | !insertmacro _EnumUsersReg_InvokeCallback $0 $R1 102 | 103 | IntOp $R0 $R0 + 1 104 | ${EndIf} 105 | ${LoopUntil} $R1 == "" 106 | 107 | # enumerate logged off users 108 | 109 | System::Call "kernel32::GetVersion() i .R0" 110 | IntOp $R0 $R0 & 0x80000000 111 | 112 | ${If} $R0 == 0 113 | # nt 114 | !insertmacro _EnumUsersReg_AdjustTokens 115 | 116 | StrCpy $R0 0 117 | ${Do} 118 | EnumRegKey $R1 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" $R0 119 | ${If} $R1 != "" 120 | ReadRegStr $R1 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$R1" \ 121 | ProfileImagePath 122 | ExpandEnvStrings $R1 $R1 123 | 124 | !insertmacro _EnumUsersReg_Load "$R1\NTUSER.DAT" $0 $1 125 | 126 | IntOp $R0 $R0 + 1 127 | ${EndIf} 128 | ${LoopUntil} $R1 == "" 129 | ${Else} 130 | # 9x 131 | ClearErrors 132 | FindFirst $R1 $R2 "$WINDIR\Profiles\*.*" 133 | ${Unless} ${Errors} 134 | ${Do} 135 | ${If} $R2 != "." 136 | ${AndIf} $R2 != ".." 137 | ${If} ${FileExists} "$WINDIR\Profiles\$R2\USER.DAT" 138 | !insertmacro _EnumUsersReg_Load "$WINDIR\Profiles\$R2\USER.DAT" $0 $1 139 | ${EndIf} 140 | ${EndIf} 141 | ClearErrors 142 | FindNext $R1 $R2 143 | ${LoopUntil} ${Errors} 144 | FindClose $R1 145 | ${EndUnless} 146 | ${Endif} 147 | 148 | Pop $R2 149 | Pop $R1 150 | Pop $R0 151 | 152 | FunctionEnd 153 | !macroend 154 | 155 | #!insertmacro _EnumUsersReg "" 156 | !insertmacro _EnumUsersReg "un." 157 | !endif 158 | -------------------------------------------------------------------------------- /installer/install.nsi: -------------------------------------------------------------------------------- 1 | Name "ChromaFiler" 2 | OutFile "..\build\ChromaFiler-install.exe" 3 | Unicode True 4 | SetCompressor LZMA 5 | !addplugindir plugins 6 | 7 | !define CHROMAFILER64 ; comment out for 32-bit 8 | 9 | !define MULTIUSER_EXECUTIONLEVEL Highest 10 | !define MULTIUSER_MUI 11 | !define MULTIUSER_INSTALLMODE_COMMANDLINE 12 | !ifdef CHROMAFILER64 13 | !define MULTIUSER_USE_PROGRAMFILES64 14 | !endif 15 | !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY "Software\ChromaFiler" 16 | !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME "InstallMode" 17 | !define MULTIUSER_INSTALLMODE_INSTDIR "ChromaFiler" 18 | !define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY "Software\ChromaFiler" 19 | !define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME "Install_Dir" 20 | !define MULTIUSER_INSTALLMODEPAGE_TEXT_TOP "$(MULTIUSER_INNERTEXT_INSTALLMODE_TOP)$\r$\n$\r$\nIf you already have $(^Name) installed, do not change this setting." 21 | !include MultiUser.nsh 22 | 23 | !include MUI2.nsh 24 | !include x64.nsh 25 | !include EnumUsersReg.nsh 26 | !include "nsis-shortcut-properties\shortcut-properties.nsh" 27 | 28 | !define REG_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\ChromaFiler" 29 | !define REG_APPPATH_KEY "Software\Microsoft\Windows\CurrentVersion\App Paths\ChromaFiler.exe" 30 | !define REG_TEXT_APPPATH_KEY "Software\Microsoft\Windows\CurrentVersion\App Paths\ChromaText.exe" 31 | !define CONTEXT_MENU_TEXT "Open in ChromaFiler" 32 | !define EXECUTE_GUID "{87612720-a94e-4fd3-a1f6-b78d7768424f}" 33 | !define EXECUTE_TEXT_GUID "{14c46e3b-9015-4bbb-9f1f-9178a94f856f}" 34 | 35 | !define MUI_ICON "..\src\res\folder.ico" 36 | !define MUI_COMPONENTSPAGE_SMALLDESC 37 | 38 | !define MUI_LICENSEPAGE_TEXT_TOP "" 39 | !define MUI_LICENSEPAGE_TEXT_BOTTOM "Click Next to continue." 40 | !define MUI_LICENSEPAGE_BUTTON "&Next >" 41 | !define MUI_TEXT_LICENSE_TITLE "Welcome to ChromaFiler Setup" 42 | !define MUI_TEXT_LICENSE_SUBTITLE "Setup will guide you through the installation of ChromaFiler." 43 | 44 | !define MUI_FINISHPAGE_RUN_TEXT "(Re)open the tray" 45 | !define MUI_FINISHPAGE_RUN 46 | !define MUI_FINISHPAGE_RUN_FUNCTION OpenTray 47 | !define MUI_FINISHPAGE_RUN_NOTCHECKED 48 | 49 | !getdllversion /productversion ..\build\ChromaFiler.exe PRODUCT_VERSION_ 50 | !ifdef CHROMAFILER64 51 | BrandingText "$(^Name) v${PRODUCT_VERSION_1}.${PRODUCT_VERSION_2}.${PRODUCT_VERSION_3} (64-bit)" 52 | !else 53 | BrandingText "$(^Name) v${PRODUCT_VERSION_1}.${PRODUCT_VERSION_2}.${PRODUCT_VERSION_3} (32-bit)" 54 | !endif 55 | 56 | !insertmacro MUI_PAGE_LICENSE licensetext.txt 57 | !insertmacro MULTIUSER_PAGE_INSTALLMODE 58 | !insertmacro MUI_PAGE_COMPONENTS 59 | !insertmacro MUI_PAGE_DIRECTORY 60 | Page Custom LockedListShow 61 | !insertmacro MUI_PAGE_INSTFILES 62 | !define MUI_PAGE_CUSTOMFUNCTION_SHOW FinishPageShow 63 | !insertmacro MUI_PAGE_FINISH 64 | 65 | !insertmacro MUI_UNPAGE_CONFIRM 66 | !insertmacro MUI_UNPAGE_COMPONENTS 67 | UninstPage Custom un.LockedListShow 68 | !insertmacro MUI_UNPAGE_INSTFILES 69 | 70 | !insertmacro MUI_LANGUAGE "English" 71 | 72 | LangString DESC_SecBase ${LANG_ENGLISH} "The main application and required components." 73 | LangString DESC_SecText ${LANG_ENGLISH} "Integrated Notepad-like text editor." 74 | LangString DESC_SecStart ${LANG_ENGLISH} "Add a shortcut to the Start Menu to launch $(^Name)." 75 | LangString DESC_SecProgID ${LANG_ENGLISH} "Add an entry to the 'Open with' menu for all file types. (Does not change the default app for any file type.)" 76 | LangString DESC_SecContext ${LANG_ENGLISH} "Add 'Open in $(^Name)' command when right-clicking a folder. Required to set default file browser." 77 | LangString DESC_SecRemoveSettings ${LANG_ENGLISH} "If unchecked, settings will be restored the next time you install ChromaFiler." 78 | LangString LOCKED_LIST_TITLE ${LANG_ENGLISH} "Close Programs" 79 | LangString LOCKED_LIST_SUBTITLE ${LANG_ENGLISH} "Make sure all $(^Name) windows are closed before continuing (including the tray)." 80 | 81 | 82 | Function .onInit 83 | InitPluginsDir 84 | !ifdef CHROMAFILER64 85 | ${IfNot} ${RunningX64} 86 | MessageBox MB_ICONSTOP "This is the 64-bit version of ChromaFiler, but you're using 32-bit Windows. Please download the 32-bit installer." 87 | Quit 88 | ${EndIf} 89 | SetRegView 64 90 | File /oname=$PLUGINSDIR\LockedList64.dll `plugins\LockedList64.dll` 91 | !else 92 | ${If} ${RunningX64} 93 | MessageBox MB_YESNO|MB_ICONEXCLAMATION|MB_DEFBUTTON2 "This is the 32-bit version of ChromaFiler, but you're using 64-bit Windows. Installing the 64-bit version is recommended. Continue anyway?" IDYES continue32bit 94 | Quit 95 | continue32bit: 96 | ${EndIf} 97 | File /oname=$PLUGINSDIR\LockedList.dll `plugins\LockedList.dll` 98 | !endif 99 | !insertmacro MULTIUSER_INIT 100 | FunctionEnd 101 | 102 | Function un.onInit 103 | InitPluginsDir 104 | !ifdef CHROMAFILER64 105 | SetRegView 64 106 | File /oname=$PLUGINSDIR\LockedList64.dll `plugins\LockedList64.dll` 107 | !else 108 | File /oname=$PLUGINSDIR\LockedList.dll `plugins\LockedList.dll` 109 | !endif 110 | !insertmacro MULTIUSER_UNINIT 111 | FunctionEnd 112 | 113 | Var tray_window 114 | !macro CUSTOM_LOCKEDLIST_PAGE 115 | FindWindow $tray_window "ChromaFile Tray" 116 | ${If} $tray_window != 0 117 | System::Call "user32.dll::PostMessage(i $tray_window, i 16, i 0, i 0)" 118 | ${EndIf} 119 | !insertmacro MUI_HEADER_TEXT $(LOCKED_LIST_TITLE) $(LOCKED_LIST_SUBTITLE) 120 | LockedList::AddModule "$INSTDIR\ChromaFiler.exe" 121 | LockedList::Dialog /autonext 122 | Pop $R0 123 | !macroend 124 | 125 | Function LockedListShow 126 | !insertmacro CUSTOM_LOCKEDLIST_PAGE 127 | FunctionEnd 128 | 129 | Function un.LockedListShow 130 | !insertmacro CUSTOM_LOCKEDLIST_PAGE 131 | FunctionEnd 132 | 133 | Function SilentSearchCallback 134 | Pop $R0 135 | Pop $R1 136 | Pop $R2 137 | ${If} $R0 != -1 138 | Push autoclose 139 | ${EndIf} 140 | FunctionEnd 141 | 142 | Section "ChromaFiler" SecBase 143 | SectionIn RO 144 | SetOutPath $INSTDIR 145 | 146 | ${If} ${Silent} 147 | LockedList::AddModule "$INSTDIR\ChromaFiler.exe" 148 | GetFunctionAddress $R0 SilentSearchCallback 149 | LockedList::SilentSearch $R0 150 | Pop $R0 151 | ${EndIf} 152 | 153 | WriteUninstaller "$INSTDIR\uninstall.exe" 154 | 155 | ; MultiUser.nsh never actually writes this value 156 | WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY}" "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME}" $MultiUser.InstallMode 157 | 158 | WriteRegStr SHCTX Software\ChromaFiler "Install_Dir" "$INSTDIR" 159 | WriteRegStr SHCTX "${REG_UNINST_KEY}" "DisplayName" "ChromaFiler" 160 | WriteRegStr SHCTX "${REG_UNINST_KEY}" "DisplayIcon" "$INSTDIR\ChromaFiler.exe,0" 161 | WriteRegStr SHCTX "${REG_UNINST_KEY}" "InstallLocation" "$INSTDIR" 162 | WriteRegStr SHCTX "${REG_UNINST_KEY}" "Publisher" "chroma zone" 163 | WriteRegStr SHCTX "${REG_UNINST_KEY}" "VersionMajor" "${PRODUCT_VERSION_1}" 164 | WriteRegStr SHCTX "${REG_UNINST_KEY}" "VersionMinor" "${PRODUCT_VERSION_2}" 165 | WriteRegStr SHCTX "${REG_UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"' 166 | WriteRegStr SHCTX "${REG_UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S' 167 | WriteRegDWORD SHCTX "${REG_UNINST_KEY}" "NoModify" 1 168 | WriteRegDWORD SHCTX "${REG_UNINST_KEY}" "NoRepair" 1 169 | 170 | File ..\build\ChromaFiler.exe 171 | File /oname=LICENSE.txt ..\LICENSE 172 | ; previous installers didn't initialize PLUGINSDIR correctly 173 | Delete $INSTDIR\LockedList64.dll 174 | Delete $INSTDIR\LockedList.dll 175 | 176 | ; https://learn.microsoft.com/en-us/windows/win32/shell/app-registration 177 | WriteRegStr SHCTX "${REG_APPPATH_KEY}" "" "$INSTDIR\ChromaFiler.exe" 178 | WriteRegStr SHCTX "${REG_APPPATH_KEY}" "DropTarget" "${EXECUTE_GUID}" 179 | 180 | WriteRegStr SHCTX "Software\Classes\CLSID\${EXECUTE_GUID}" "" "ChromaFiler" 181 | WriteRegStr SHCTX "Software\Classes\CLSID\${EXECUTE_GUID}\LocalServer32" "" "$INSTDIR\ChromaFiler.exe" 182 | SectionEnd 183 | 184 | Section "ChromaText (text editor)" SecText 185 | File ..\build\ChromaText.exe 186 | 187 | WriteRegStr SHCTX "${REG_TEXT_APPPATH_KEY}" "" "$INSTDIR\ChromaText.exe" 188 | WriteRegStr SHCTX "${REG_TEXT_APPPATH_KEY}" "DropTarget" "${EXECUTE_TEXT_GUID}" 189 | 190 | WriteRegStr SHCTX "Software\Classes\CLSID\${EXECUTE_TEXT_GUID}" "" "ChromaText" 191 | WriteRegStr SHCTX "Software\Classes\CLSID\${EXECUTE_TEXT_GUID}\LocalServer32" "" "$INSTDIR\ChromaFiler.exe" 192 | SectionEnd 193 | 194 | Section "Start Menu shortcut" SecStart 195 | ; SMPROGRAMS will be set by MultiUser 196 | CreateShortcut /NoWorkingDir "$SMPROGRAMS\ChromaFiler.lnk" "$INSTDIR\ChromaFiler.exe" 197 | !insertmacro ShortcutSetToastProperties "$SMPROGRAMS\ChromaFiler.lnk" "{bcf1926f-5819-497a-93b6-dc2b165ddd9c}" "chroma.file" 198 | ${If} ${SectionIsSelected} ${SecText} 199 | Call CreateChromaTextShortcut 200 | ${EndIf} 201 | SectionEnd 202 | 203 | Function CreateChromaTextShortcut 204 | CreateShortcut /NoWorkingDir "$SMPROGRAMS\ChromaText.lnk" "$INSTDIR\ChromaText.exe" 205 | !insertmacro ShortcutSetToastProperties "$SMPROGRAMS\ChromaText.lnk" "{bcf1926f-5819-497a-93b6-dc2b165ddd9c}" "chroma.text" 206 | FunctionEnd 207 | 208 | Section "Add to Open With menu" SecProgID 209 | ; https://learn.microsoft.com/en-us/windows/win32/shell/customizing-file-types-bumper 210 | WriteRegStr SHCTX "Software\Classes\Applications\ChromaFiler.exe" "FriendlyAppName" "ChromaFiler Preview" 211 | WriteRegStr SHCTX "Software\Classes\Applications\ChromaFiler.exe" "AppUserModelID" "chroma.file" 212 | WriteRegStr SHCTX "Software\Classes\Applications\ChromaFiler.exe\shell\open\command" "" '"$INSTDIR\ChromaFiler.exe" "%1"' 213 | WriteRegStr SHCTX "Software\Classes\Applications\ChromaFiler.exe\shell\open\command" "DelegateExecute" "${EXECUTE_GUID}" 214 | ; hack for ArsClip (TODO: add to all users?) 215 | WriteRegStr HKCU "Software\Classes\Applications\ChromaFiler.exe\shell\open\command" "" '"$INSTDIR\ChromaFiler.exe" "%1"' 216 | ; clean up old icon (removed v0.7.0) 217 | DeleteRegKey SHCTX "Software\Classes\Applications\ChromaFiler.exe\DefaultIcon" 218 | 219 | ${If} ${SectionIsSelected} ${SecText} 220 | ; Application 221 | WriteRegStr SHCTX "Software\Classes\Applications\ChromaText.exe" "FriendlyAppName" "ChromaText" 222 | WriteRegStr SHCTX "Software\Classes\Applications\ChromaText.exe" "AppUserModelID" "chroma.text" 223 | WriteRegStr SHCTX "Software\Classes\Applications\ChromaText.exe" "NoOpenWith" "" 224 | WriteRegStr SHCTX "Software\Classes\Applications\ChromaText.exe\shell\open\command" "" '"$INSTDIR\ChromaText.exe" "%1"' 225 | WriteRegStr SHCTX "Software\Classes\Applications\ChromaText.exe\shell\open\command" "DelegateExecute" "${EXECUTE_TEXT_GUID}" 226 | ; ProgID 227 | WriteRegStr SHCTX "Software\Classes\Chroma.Text" "" "ChromaText" 228 | WriteRegStr SHCTX "Software\Classes\Chroma.Text" "AppUserModelID" "chroma.text" 229 | WriteRegStr SHCTX "Software\Classes\Chroma.Text\DefaultIcon" "" "C:\Windows\System32\imageres.dll,-102" 230 | WriteRegStr SHCTX "Software\Classes\Chroma.Text\shell\open\command" "" '"$INSTDIR\ChromaText.exe" "%1"' 231 | WriteRegStr SHCTX "Software\Classes\Chroma.Text\shell\open\command" "DelegateExecute" "${EXECUTE_TEXT_GUID}" 232 | WriteRegStr SHCTX "Software\Classes\*\OpenWithProgids" "Chroma.Text" "" 233 | ; preview handler 234 | WriteRegStr SHCTX "Software\Classes\Chroma.Text\shellex\{8895b1c6-b41f-4c1c-a562-0d564250836f}" "" "{1531d583-8375-4d3f-b5fb-d23bbd169f22}" 235 | ; hack 236 | WriteRegStr HKCU "Software\Classes\Chroma.Text\shell\open\command" "" '"$INSTDIR\ChromaText.exe" "%1"' 237 | ${EndIf} 238 | SectionEnd 239 | 240 | Section "Add to folder context menu" SecContext 241 | Var /GLOBAL default_browser 242 | ; clear shell defaults if empty / not defined 243 | ReadRegStr $default_browser SHCTX "Software\Classes\Directory\Shell" "" 244 | StrCmp $default_browser "" 0 +2 245 | WriteRegStr SHCTX "Software\Classes\Directory\Shell" "" "none" 246 | ReadRegStr $default_browser SHCTX "Software\Classes\CompressedFolder\Shell" "" 247 | StrCmp $default_browser "" 0 +2 248 | WriteRegStr SHCTX "Software\Classes\CompressedFolder\Shell" "" "none" 249 | ReadRegStr $default_browser SHCTX "Software\Classes\Drive\Shell" "" 250 | StrCmp $default_browser "" 0 +2 251 | WriteRegStr SHCTX "Software\Classes\Drive\Shell" "" "none" 252 | 253 | WriteRegStr SHCTX Software\Classes\Directory\shell\chromafiler "" "${CONTEXT_MENU_TEXT}" 254 | WriteRegStr SHCTX Software\Classes\Directory\Background\shell\chromafiler "" "${CONTEXT_MENU_TEXT}" 255 | WriteRegStr SHCTX Software\Classes\CompressedFolder\shell\chromafiler "" "${CONTEXT_MENU_TEXT}" 256 | WriteRegStr SHCTX Software\Classes\Drive\shell\chromafiler "" "${CONTEXT_MENU_TEXT}" 257 | 258 | WriteRegStr SHCTX Software\Classes\Directory\shell\chromafiler "Icon" "$INSTDIR\ChromaFiler.exe" 259 | WriteRegStr SHCTX Software\Classes\Directory\Background\shell\chromafiler "Icon" "$INSTDIR\ChromaFiler.exe" 260 | WriteRegStr SHCTX Software\Classes\CompressedFolder\shell\chromafiler "Icon" "$INSTDIR\ChromaFiler.exe" 261 | WriteRegStr SHCTX Software\Classes\Drive\shell\chromafiler "Icon" "$INSTDIR\ChromaFiler.exe" 262 | 263 | ; https://superuser.com/questions/136838/which-special-variables-are-available-when-writing-a-shell-command-for-a-context 264 | ; explorer uses: %SystemRoot%\Explorer.exe /idlist,%I,%L 265 | Var /GLOBAL context_menu_command 266 | StrCpy $context_menu_command '"$INSTDIR\ChromaFiler.exe" "%v"' 267 | WriteRegStr SHCTX Software\Classes\Directory\shell\chromafiler\command "" '$context_menu_command' 268 | WriteRegStr SHCTX Software\Classes\Directory\Background\shell\chromafiler\command "" '$context_menu_command' 269 | WriteRegStr SHCTX Software\Classes\CompressedFolder\shell\chromafiler\command "" '$context_menu_command' 270 | WriteRegStr SHCTX Software\Classes\Drive\shell\chromafiler\command "" '$context_menu_command' 271 | 272 | WriteRegStr SHCTX Software\Classes\Directory\shell\chromafiler\command "DelegateExecute" "${EXECUTE_GUID}" 273 | WriteRegStr SHCTX Software\Classes\Directory\Background\shell\chromafiler\command "DelegateExecute" "${EXECUTE_GUID}" 274 | WriteRegStr SHCTX Software\Classes\CompressedFolder\shell\chromafiler\command "DelegateExecute" "${EXECUTE_GUID}" 275 | WriteRegStr SHCTX Software\Classes\Drive\shell\chromafiler\command "DelegateExecute" "${EXECUTE_GUID}" 276 | SectionEnd 277 | 278 | Function FinishPageShow 279 | ${If} $tray_window != 0 280 | SendMessage $mui.FinishPage.Run ${BM_SETCHECK} ${BST_CHECKED} 0 281 | ${EndIf} 282 | FunctionEnd 283 | 284 | Function OpenTray 285 | ; https://mdb-blog.blogspot.com/2013/01/nsis-lunch-program-as-user-from-uac.html 286 | CreateShortCut "$TEMP\ChromaFilerTray.lnk" "$INSTDIR\ChromaFiler.exe" "/tray" 287 | Exec '"$WINDIR\explorer.exe" "$TEMP\ChromaFilerTray.lnk"' 288 | FunctionEnd 289 | 290 | Section "un.Uninstall ChromaFiler" 291 | SectionIn RO 292 | 293 | Delete $INSTDIR\*.exe 294 | Delete $INSTDIR\LICENSE.txt 295 | RMDir $INSTDIR 296 | DeleteRegKey SHCTX "${REG_UNINST_KEY}" 297 | DeleteRegKey SHCTX "${REG_APPPATH_KEY}" 298 | DeleteRegKey SHCTX "${REG_TEXT_APPPATH_KEY}" 299 | DeleteRegKey SHCTX "Software\Classes\Applications\ChromaFiler.exe" 300 | DeleteRegKey SHCTX "Software\Classes\Applications\ChromaText.exe" 301 | DeleteRegKey SHCTX "Software\Classes\Chroma.Text" 302 | DeleteRegValue SHCTX "Software\Classes\*\OpenWithProgids" "Chroma.Text" 303 | DeleteRegKey SHCTX Software\Classes\Directory\shell\chromafiler 304 | DeleteRegKey SHCTX Software\Classes\Directory\Background\shell\chromafiler 305 | DeleteRegKey SHCTX Software\Classes\CompressedFolder\shell\chromafiler 306 | DeleteRegKey SHCTX Software\Classes\Drive\shell\chromafiler 307 | DeleteRegKey SHCTX "Software\Classes\CLSID\${EXECUTE_GUID}" 308 | DeleteRegKey SHCTX "Software\Classes\CLSID\${EXECUTE_TEXT_GUID}" 309 | 310 | Delete $SMPROGRAMS\ChromaFiler.lnk 311 | Delete $SMPROGRAMS\ChromaText.lnk 312 | 313 | ; Clean up hacks 314 | DeleteRegKey HKCU "Software\Classes\Applications\ChromaFiler.exe" 315 | DeleteRegKey HKCU "Software\Classes\Chroma.Text" 316 | 317 | ; Clean up settings written by ChromaFiler (could be under multiple users) 318 | ${If} $MultiUser.InstallMode == "CurrentUser" 319 | Call un.CleanupCurrentUser 320 | ${Else} 321 | ${un.EnumUsersReg} un.CleanupUser chromafiler.temp 322 | ${EndIf} 323 | SectionEnd 324 | 325 | Section "un.Remove all settings" SecRemoveSettings 326 | DeleteRegKey SHCTX Software\ChromaFiler 327 | DeleteRegKey HKCU "Software\ChromaFiler" 328 | ; Other users' settings will not be erased 329 | SectionEnd 330 | 331 | Function un.CleanupCurrentUser 332 | DeleteRegValue HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" "ChromaFiler" 333 | 334 | ; clear shell defaults if set to chromafiler 335 | ReadRegStr $default_browser HKCU "Software\Classes\Directory\Shell" "" 336 | StrCmp $default_browser "chromafiler" 0 +2 337 | WriteRegStr HKCU "Software\Classes\Directory\Shell" "" "none" 338 | ReadRegStr $default_browser HKCU "Software\Classes\CompressedFolder\Shell" "" 339 | StrCmp $default_browser "chromafiler" 0 +2 340 | WriteRegStr HKCU "Software\Classes\CompressedFolder\Shell" "" "none" 341 | ReadRegStr $default_browser HKCU "Software\Classes\Drive\Shell" "" 342 | StrCmp $default_browser "chromafiler" 0 +2 343 | WriteRegStr HKCU "Software\Classes\Drive\Shell" "" "none" 344 | FunctionEnd 345 | 346 | Function un.CleanupUser 347 | Pop $0 348 | DeleteRegValue HKU "$0\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" "ChromaFiler" 349 | 350 | ; clear shell defaults if set to chromafiler 351 | ReadRegStr $default_browser HKU "$0\Software\Classes\Directory\Shell" "" 352 | StrCmp $default_browser "chromafiler" 0 +2 353 | WriteRegStr HKU "$0\Software\Classes\Directory\Shell" "" "none" 354 | ReadRegStr $default_browser HKU "$0\Software\Classes\CompressedFolder\Shell" "" 355 | StrCmp $default_browser "chromafiler" 0 +2 356 | WriteRegStr HKU "$0\Software\Classes\CompressedFolder\Shell" "" "none" 357 | ReadRegStr $default_browser HKU "$0\Software\Classes\Drive\Shell" "" 358 | StrCmp $default_browser "chromafiler" 0 +2 359 | WriteRegStr HKU "$0\Software\Classes\Drive\Shell" "" "none" 360 | FunctionEnd 361 | 362 | !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN 363 | !insertmacro MUI_DESCRIPTION_TEXT ${SecBase} $(DESC_SecBase) 364 | !insertmacro MUI_DESCRIPTION_TEXT ${SecText} $(DESC_SecText) 365 | !insertmacro MUI_DESCRIPTION_TEXT ${SecStart} $(DESC_SecStart) 366 | !insertmacro MUI_DESCRIPTION_TEXT ${SecProgID} $(DESC_SecProgID) 367 | !insertmacro MUI_DESCRIPTION_TEXT ${SecContext} $(DESC_SecContext) 368 | !insertmacro MUI_FUNCTION_DESCRIPTION_END 369 | 370 | !insertmacro MUI_UNFUNCTION_DESCRIPTION_BEGIN 371 | !insertmacro MUI_DESCRIPTION_TEXT ${SecRemoveSettings} $(DESC_SecRemoveSettings) 372 | !insertmacro MUI_UNFUNCTION_DESCRIPTION_END 373 | -------------------------------------------------------------------------------- /installer/licensetext.txt: -------------------------------------------------------------------------------- 1 | LICENSE INFORMATION 2 | 3 | ChromaFiler is available under the GNU General Public License v3.0. 4 | A copy of the license will be installed along with the program. 5 | The source code is available at https://github.com/vanjac/chromafiler 6 | 7 | The ChromaFiler installer uses the following open source libraries: 8 | - nsis-shortcut-properties 9 | Copyright Safing ICS Technologies GmbH 10 | Licensed under the Apache License 2.0 11 | (https://www.apache.org/licenses/LICENSE-2.0) 12 | https://github.com/safing/nsis-shortcut-properties 13 | - LockedList plug-in 14 | Copyright (c) 2013 Afrow Soft Ltd 15 | Licensed under the zlib License 16 | https://nsis.sourceforge.io/LockedList_plug-in 17 | -------------------------------------------------------------------------------- /installer/plugins/LockedList.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanjac/chromafiler/fde6bd6a8a80a749c8565822f581500c927494ab/installer/plugins/LockedList.dll -------------------------------------------------------------------------------- /installer/plugins/LockedList.txt: -------------------------------------------------------------------------------- 1 | https://nsis.sourceforge.io/LockedList_plug-in (unicode version) 2 | 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | License 5 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | Copyright (c) 2013 Afrow Soft Ltd 8 | 9 | This software is provided 'as-is', without any express or implied 10 | warranty. In no event will the authors be held liable for any damages 11 | arising from the use of this software. 12 | 13 | Permission is granted to anyone to use this software for any purpose, 14 | including commercial applications, and to alter it and redistribute 15 | it freely, subject to the following restrictions: 16 | 17 | 1. The origin of this software must not be misrepresented; 18 | you must not claim that you wrote the original software. 19 | If you use this software in a product, an acknowledgment in the 20 | product documentation would be appreciated but is not required. 21 | 2. Altered versions must be plainly marked as such, 22 | and must not be misrepresented as being the original software. 23 | 3. This notice may not be removed or altered from any distribution. 24 | -------------------------------------------------------------------------------- /installer/plugins/LockedList64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanjac/chromafiler/fde6bd6a8a80a749c8565822f581500c927494ab/installer/plugins/LockedList64.dll -------------------------------------------------------------------------------- /src/COMUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "COMUtils.h" 2 | #include 3 | 4 | namespace chromafiler { 5 | 6 | // https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/implementing-iunknown-in-c-plus-plus 7 | 8 | STDMETHODIMP UnknownImpl::QueryInterface(REFIID id, void **obj) { 9 | if (!obj) 10 | return E_INVALIDARG; 11 | *obj = nullptr; 12 | if (id == __uuidof(IUnknown)) { 13 | *obj = this; 14 | AddRef(); 15 | return S_OK; 16 | } 17 | return E_NOINTERFACE; 18 | } 19 | 20 | STDMETHODIMP_(ULONG) UnknownImpl::AddRef() { 21 | return InterlockedIncrement(&refCount); 22 | } 23 | 24 | STDMETHODIMP_(ULONG) UnknownImpl::Release() { 25 | long r = InterlockedDecrement(&refCount); 26 | if (r == 0) { 27 | delete this; 28 | } 29 | return r; 30 | } 31 | 32 | 33 | StoppableThread::StoppableThread() { 34 | stopEvent = checkLE(CreateEvent(nullptr, TRUE, FALSE, nullptr)); 35 | } 36 | 37 | StoppableThread::~StoppableThread() { 38 | checkLE(CloseHandle(thread)); 39 | checkLE(CloseHandle(stopEvent)); 40 | } 41 | 42 | void StoppableThread::start() { 43 | AddRef(); 44 | if (!SHCreateThreadWithHandle(threadProc, this, CTF_COINIT_STA, nullptr, &thread)) 45 | Release(); 46 | } 47 | 48 | void StoppableThread::stop() { 49 | AcquireSRWLockExclusive(&stopLock); 50 | checkLE(SetEvent(stopEvent)); 51 | ReleaseSRWLockExclusive(&stopLock); 52 | } 53 | 54 | bool StoppableThread::isStopped() { 55 | return WaitForSingleObject(stopEvent, 0) == WAIT_OBJECT_0; 56 | } 57 | 58 | DWORD WINAPI StoppableThread::threadProc(void *data) { 59 | StoppableThread *self = (StoppableThread *)data; 60 | self->run(); 61 | self->Release(); 62 | return 0; 63 | } 64 | 65 | } // namespace 66 | -------------------------------------------------------------------------------- /src/COMUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "main.h" 5 | #include 6 | 7 | namespace chromafiler { 8 | 9 | class UnknownImpl : public IUnknown { 10 | public: 11 | virtual ~UnknownImpl() = default; 12 | 13 | // IUnknown 14 | STDMETHODIMP QueryInterface(REFIID id, void **obj) override; 15 | STDMETHODIMP_(ULONG) AddRef() override; 16 | STDMETHODIMP_(ULONG) Release() override; 17 | 18 | private: 19 | long refCount = 1; 20 | }; 21 | 22 | template 23 | class ClassFactoryImpl : public IClassFactory { 24 | // IUnknown 25 | STDMETHODIMP_(ULONG) AddRef() override { return 2; } 26 | STDMETHODIMP_(ULONG) Release() override { return 1; } 27 | STDMETHODIMP QueryInterface(REFIID id, void **obj) override { 28 | static const QITAB interfaces[] = { 29 | QITABENT(ClassFactoryImpl, IClassFactory), 30 | {}, 31 | }; 32 | return QISearch(this, interfaces, id, obj); 33 | } 34 | // IClassFactory 35 | STDMETHODIMP CreateInstance(IUnknown *outer, REFIID id, void **obj) override { 36 | *obj = nullptr; 37 | if (outer) 38 | return CLASS_E_NOAGGREGATION; 39 | CComPtr inst; 40 | inst.Attach(new T()); 41 | HRESULT hr = inst->QueryInterface(id, obj); 42 | return hr; 43 | } 44 | STDMETHODIMP LockServer(BOOL lock) override { 45 | if (keepOpen) { 46 | if (lock) 47 | lockProcess(); 48 | else 49 | unlockProcess(); 50 | } 51 | return S_OK; 52 | } 53 | }; 54 | 55 | class StoppableThread : public UnknownImpl { 56 | public: 57 | StoppableThread(); 58 | virtual ~StoppableThread(); 59 | void start(); // do not call multiple times! 60 | void stop(); 61 | 62 | protected: 63 | virtual void run() = 0; 64 | 65 | bool isStopped(); 66 | HANDLE stopEvent; 67 | SRWLOCK stopLock = SRWLOCK_INIT; // thread will not be stopped while held 68 | 69 | private: 70 | HANDLE thread = nullptr; 71 | 72 | static DWORD WINAPI threadProc(void *); 73 | }; 74 | 75 | } // namespace 76 | -------------------------------------------------------------------------------- /src/ChainWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "ChainWindow.h" 2 | #include "ItemWindow.h" 3 | 4 | namespace chromafiler { 5 | 6 | const wchar_t CHAIN_OWNER_CLASS[] = L"ChromaFile Chain"; 7 | 8 | void ChainWindow::init() { 9 | WNDCLASS chainClass = {}; 10 | chainClass.lpszClassName = CHAIN_OWNER_CLASS; 11 | chainClass.lpfnWndProc = windowProc; 12 | chainClass.hInstance = GetModuleHandle(nullptr); 13 | RegisterClass(&chainClass); 14 | } 15 | 16 | ChainWindow::ChainWindow(ItemWindow *left, bool leftIsPopup, int showCommand) 17 | : left(left) { 18 | debugPrintf(L"Create chain\n"); 19 | // there are special cases here for popup windows (ie. the tray) to fix DPI scaling bugs. 20 | // see ItemWindow::windowRectChanged() for details 21 | HWND window = checkLE(CreateWindowEx(leftIsPopup ? (WS_EX_LAYERED | WS_EX_TOOLWINDOW) : 0, 22 | CHAIN_OWNER_CLASS, nullptr, leftIsPopup ? WS_OVERLAPPED : WS_POPUP, 0, 0, 0, 0, 23 | nullptr, nullptr, GetModuleHandle(nullptr), (WindowImpl *)this)); 24 | if (showCommand != -1) 25 | ShowWindow(window, showCommand); // show in taskbar 26 | if (leftIsPopup) 27 | SetLayeredWindowAttributes(window, 0, 0, LWA_ALPHA); // invisible but still drawn 28 | } 29 | 30 | ChainWindow::~ChainWindow() { 31 | debugPrintf(L"Destroy chain\n"); 32 | setPreview(nullptr); 33 | DestroyWindow(hwnd); 34 | } 35 | 36 | HWND ChainWindow::getWnd() { 37 | return hwnd; 38 | } 39 | 40 | void ChainWindow::setLeft(ItemWindow *window) { 41 | left = window; 42 | } 43 | 44 | void ChainWindow::setPreview(HWND newPreview) { 45 | CComPtr taskbar; 46 | if (!checkHR(taskbar.CoCreateInstance(__uuidof(TaskbarList)))) 47 | return; 48 | 49 | if (preview) { 50 | checkHR(taskbar->UnregisterTab(preview)); 51 | } 52 | preview = newPreview; 53 | if (preview) { 54 | checkHR(taskbar->RegisterTab(preview, hwnd)); 55 | checkHR(taskbar->SetTabOrder(preview, nullptr)); 56 | checkHR(taskbar->SetTabProperties(preview, STPF_USEAPPPEEKALWAYS)); 57 | } 58 | } 59 | 60 | void ChainWindow::setText(const wchar_t *text) { 61 | SetWindowText(hwnd, text); 62 | } 63 | 64 | void ChainWindow::setIcon(HICON smallIcon, HICON largeIcon) { 65 | SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)smallIcon); 66 | SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)largeIcon); 67 | } 68 | 69 | void ChainWindow::setEnabled(bool enabled) { 70 | for (ItemWindow *window = left; window; window = window->child) 71 | EnableWindow(window->hwnd, enabled); 72 | } 73 | 74 | LRESULT ChainWindow::handleMessage(UINT message, WPARAM wParam, LPARAM lParam) { 75 | switch (message) { 76 | case WM_CLOSE: 77 | // default behavior is to destroy owned windows without calling WM_CLOSE. 78 | // instead close the left-most chain window to give user a chance to save. 79 | debugPrintf(L"Close chain\n"); 80 | if (left->paletteWindow()) { 81 | if (left->child) 82 | left->child->close(); 83 | } else { 84 | left->close(); 85 | } 86 | return 0; 87 | } 88 | return DefWindowProc(hwnd, message, wParam, lParam); 89 | } 90 | 91 | } // namespace 92 | -------------------------------------------------------------------------------- /src/ChainWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | 4 | #include "COMUtils.h" 5 | #include "WinUtils.h" 6 | 7 | namespace chromafiler { 8 | 9 | class ItemWindow; 10 | 11 | class ChainWindow : public WindowImpl, public UnknownImpl { 12 | public: 13 | static void init(); 14 | 15 | // window lifetime is controlled by object lifetime (unlike ItemWindow) 16 | ChainWindow(ItemWindow *left, bool leftIsPopup = false, int showCommand = -1); 17 | ~ChainWindow(); 18 | 19 | HWND getWnd(); 20 | 21 | void setLeft(ItemWindow *window); 22 | void setPreview(HWND newPreview); 23 | void setText(const wchar_t *text); 24 | void setIcon(HICON smallIcon, HICON largeIcon); 25 | void setEnabled(bool enabled); 26 | 27 | protected: 28 | LRESULT handleMessage(UINT message, WPARAM wParam, LPARAM lParam) override; 29 | 30 | private: 31 | ItemWindow *left; // left-most window 32 | HWND preview = nullptr; // taskbar preview window 33 | }; 34 | 35 | } // namespace 36 | -------------------------------------------------------------------------------- /src/CreateItemWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "CreateItemWindow.h" 2 | #include "FolderWindow.h" 3 | #include "ThumbnailView.h" 4 | #include "PreviewWindow.h" 5 | #include "TextWindow.h" 6 | #include "Settings.h" 7 | #include "ShellUtils.h" 8 | #include "UIStrings.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace chromafiler { 17 | 18 | const wchar_t IPreviewHandlerIID[] = L"{8895b1c6-b41f-4c1c-a562-0d564250836f}"; 19 | // Windows TXT Previewer {1531d583-8375-4d3f-b5fb-d23bbd169f22} 20 | const CLSID TXT_PREVIEWER_CLSID = 21 | {0x1531d583, 0x8375, 0x4d3f, {0xb5, 0xfb, 0xd2, 0x3b, 0xbd, 0x16, 0x9f, 0x22}}; 22 | 23 | bool previewHandlerCLSID(wchar_t *type, CLSID *previewID); 24 | bool isCFWindow(HWND hwnd); 25 | 26 | CComPtr createItemWindow(ItemWindow *const parent, IShellItem *const item) { 27 | CComPtr window; 28 | SFGAOF attr; 29 | if (checkHR(item->GetAttributes(SFGAO_FOLDER, &attr)) && (attr & SFGAO_FOLDER)) { 30 | window.Attach(new FolderWindow(parent, item)); 31 | return window; 32 | } 33 | 34 | bool previewsEnabled = settings::getPreviewsEnabled(); 35 | bool textEditorEnabled = settings::getTextEditorEnabled(); 36 | if (previewsEnabled || textEditorEnabled) { 37 | CComQIPtr item2(item); 38 | CComHeapPtr type; 39 | if (item2 && SUCCEEDED(item2->GetString(PKEY_ItemType, &type))) { 40 | CLSID previewID; 41 | if (textEditorEnabled && lstrcmp(type, L".") == 0) { // no extension 42 | window.Attach(new TextWindow(parent, item)); 43 | return window; 44 | } else if (previewHandlerCLSID(type, &previewID)) { 45 | if (textEditorEnabled && previewID == TXT_PREVIEWER_CLSID) { 46 | window.Attach(new TextWindow(parent, item)); 47 | return window; 48 | } else if (previewsEnabled) { 49 | window.Attach(new PreviewWindow(parent, item, previewID)); 50 | return window; 51 | } 52 | } 53 | } 54 | } 55 | window.Attach(new PreviewWindow(parent, item, CLSID_ThumbnailView, false)); 56 | return window; 57 | } 58 | 59 | bool previewHandlerCLSID(wchar_t *type, CLSID *previewID) { 60 | // https://geelaw.blog/entries/ipreviewhandlerframe-wpf-1-ui-assoc/ 61 | wchar_t resultGUID[64]; 62 | DWORD resultLen = _countof(resultGUID); 63 | if (FAILED(AssocQueryString(ASSOCF_INIT_DEFAULTTOSTAR | ASSOCF_NOTRUNCATE, 64 | ASSOCSTR_SHELLEXTENSION, type, IPreviewHandlerIID, resultGUID, &resultLen))) 65 | return false; 66 | debugPrintf(L"Found preview handler for %s: %s\n", type, resultGUID); 67 | return checkHR(CLSIDFromString(resultGUID, previewID)); 68 | } 69 | 70 | bool showItemWindow(IShellItem *const item, IShellWindows *const shellWindows, int showCmd) { 71 | CComQIPtr persistIDList(item); 72 | if (!persistIDList) 73 | return false; 74 | CComVariant empty, pidlVar(persistIDList); 75 | long lWnd; 76 | CComPtr dispatch; // ignored 77 | // TODO: check all windows? special case for text? 78 | HRESULT hr = shellWindows->FindWindowSW( 79 | &pidlVar, &empty, SWC_BROWSER, &lWnd, 0, &dispatch); // don't require dispatch! 80 | checkHR(hr); 81 | if (hr == S_OK) { 82 | HWND hwnd = (HWND)LongToHandle(lWnd); 83 | if (isCFWindow(hwnd)) { 84 | debugPrintf(L"Found already-open window\n"); 85 | SetForegroundWindow(hwnd); 86 | ShowWindow(hwnd, showCmd); 87 | ItemWindow::flashWindow(hwnd); 88 | return true; 89 | } 90 | } 91 | return false; 92 | } 93 | 94 | CComPtr resolveLink(IShellItem *const linkItem) { 95 | // https://stackoverflow.com/a/46064112 96 | CComPtr link; 97 | if (SUCCEEDED(linkItem->BindToHandler(nullptr, BHID_SFUIObject, IID_PPV_ARGS(&link)))) { 98 | if (checkHR(link->Resolve(nullptr, SLR_NO_UI | SLR_UPDATE))) { 99 | CComHeapPtr targetPIDL; 100 | if (checkHR(link->GetIDList(&targetPIDL))) { 101 | CComPtr targetItem; 102 | if (checkHR(SHCreateItemFromIDList(targetPIDL, IID_PPV_ARGS(&targetItem)))) { 103 | SFGAOF attr; 104 | if (FAILED(targetItem->GetAttributes(SFGAO_VALIDATE, &attr))) // doesn't exist 105 | return linkItem; 106 | // don't need to recurse, shortcuts to shortcuts are not allowed 107 | return targetItem; 108 | } 109 | } 110 | } 111 | } 112 | return linkItem; 113 | } 114 | 115 | CComPtr itemFromPath(wchar_t *path) { 116 | while (1) { 117 | CComPtr item; 118 | // parse name vs display name https://stackoverflow.com/q/42966489 119 | if (checkHR(SHCreateItemFromParsingName(path, nullptr, IID_PPV_ARGS(&item)))) 120 | return item; 121 | int result = MessageBox(nullptr, formatString(IDS_CANT_FIND_ITEM, path).get(), 122 | getString(IDS_ERROR_CAPTION), MB_CANCELTRYCONTINUE | MB_ICONERROR); 123 | if (result == IDCANCEL) { 124 | return nullptr; 125 | } else if (result == IDCONTINUE) { 126 | if (checkHR(SHGetKnownFolderItem(FOLDERID_Desktop, KF_FLAG_DEFAULT, nullptr, 127 | IID_PPV_ARGS(&item)))) 128 | return item; 129 | } // else retry 130 | } 131 | } 132 | 133 | CComPtr createScratchFile(IShellItem *const folder) { 134 | CComPtr operation; 135 | if (!checkHR(operation.CoCreateInstance(__uuidof(FileOperation)))) 136 | return nullptr; 137 | checkHR(operation->SetOperationFlags( 138 | (IsWindows8OrGreater() ? FOFX_ADDUNDORECORD : FOF_ALLOWUNDO) | FOF_RENAMEONCOLLISION)); 139 | wstr_ptr fileName = settings::getScratchFileName(); 140 | NewItemSink eventSink; 141 | checkHR(operation->NewItem(folder, FILE_ATTRIBUTE_NORMAL, fileName.get(), nullptr, &eventSink)); 142 | checkHR(operation->PerformOperations()); 143 | return eventSink.newItem; 144 | } 145 | 146 | bool isCFWindow(HWND hwnd) { 147 | wchar_t className[64] = L""; 148 | if (!checkLE(GetClassName(hwnd, className, _countof(className)))) 149 | return false; 150 | const wchar_t prefix[] = L"ChromaFile"; 151 | className[_countof(prefix) - 1] = 0; 152 | return lstrcmpi(className, prefix) == 0; 153 | } 154 | 155 | 156 | void debugDisplayNames(HWND hwnd, IShellItem *const item) { 157 | static SIGDN nameTypes[] = { 158 | SIGDN_NORMALDISPLAY, SIGDN_PARENTRELATIVE, 159 | SIGDN_PARENTRELATIVEEDITING, SIGDN_PARENTRELATIVEFORUI, 160 | SIGDN_PARENTRELATIVEFORADDRESSBAR, SIGDN_FILESYSPATH, 161 | SIGDN_DESKTOPABSOLUTEEDITING, SIGDN_DESKTOPABSOLUTEPARSING, 162 | SIGDN_PARENTRELATIVEPARSING, SIGDN_URL 163 | }; 164 | CComHeapPtr names[_countof(nameTypes)]; 165 | for (int i = 0; i < _countof(names); i++) 166 | checkHR(item->GetDisplayName(nameTypes[i], &names[i])); 167 | static const PROPERTYKEY *pkeys[] = { 168 | // https://learn.microsoft.com/en-us/windows/win32/properties/core-bumper 169 | &PKEY_ItemName, &PKEY_ItemNameDisplay, 170 | &PKEY_ItemNameDisplayWithoutExtension, 171 | &PKEY_ItemPathDisplay, &PKEY_ItemType, 172 | &PKEY_FileName, &PKEY_FileExtension, 173 | // https://learn.microsoft.com/en-us/windows/win32/properties/shell-bumper 174 | &PKEY_NamespaceCLSID, 175 | }; 176 | CComHeapPtr props[_countof(pkeys)]; 177 | CComQIPtr item2(item); 178 | if (item2) { 179 | for (int i = 0; i < _countof(pkeys); i++) 180 | checkHR(item2->GetString(*pkeys[i], &props[i])); 181 | } 182 | showDebugMessage(hwnd, L"Item Display Names", L"" 183 | "Normal Display:\t\t%1\r\n" "Parent Relative:\t\t%2\r\n" 184 | "Parent Relative Editing:\t%3\r\n" "Parent Relative UI (Win8):\t%4\r\n" 185 | "Parent Relative Address Bar:\t%5\r\n" "File System Path:\t\t%6\r\n" 186 | "Desktop Absolute Editing:\t%7\r\n" "Desktop Absolute Parsing:\t%8\r\n" 187 | "Parent Relative Parsing:\t%9\r\n" "URL:\t\t\t%10\r\n" 188 | "System.ItemName:\t\t%11\r\n" "System.ItemNameDisplay:\t%12\r\n" 189 | "System.ItemNameDisplayWithoutExtension: %13\r\n" 190 | "System.ItemPathDisplay:\t%14\r\n" "System.ItemType:\t\t%15\r\n" 191 | "System.FileName:\t\t%16\r\n" "System.FileExtension:\t%17\r\n" 192 | "System.NamespaceCLSID:\t%18", 193 | &*names[0], &*names[1], &*names[2], &*names[3], &*names[4], &*names[5], 194 | &*names[6], &*names[7], &*names[8], &*names[9], 195 | &*props[0], &*props[1], &*props[2], &*props[3], &*props[4], &*props[5], 196 | &*props[6], &*props[7]); 197 | } 198 | 199 | } // namespace 200 | -------------------------------------------------------------------------------- /src/CreateItemWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "ItemWindow.h" 5 | #include 6 | 7 | namespace chromafiler { 8 | 9 | CComPtr createItemWindow(ItemWindow *parent, IShellItem *item); 10 | bool showItemWindow(IShellItem *item, IShellWindows *shellWindows, int showCmd); 11 | CComPtr resolveLink(IShellItem *linkItem); 12 | // displays error message if item can't be found 13 | CComPtr itemFromPath(wchar_t *path); 14 | CComPtr createScratchFile(IShellItem *folder); 15 | 16 | void debugDisplayNames(HWND hwnd, IShellItem *item); 17 | 18 | } // namespace 19 | -------------------------------------------------------------------------------- /src/DPI.cpp: -------------------------------------------------------------------------------- 1 | #include "DPI.h" 2 | 3 | namespace chromafiler { 4 | 5 | // https://github.com/tringi/win32-dpi 6 | 7 | // requires Windows 8.1 8 | static HRESULT (WINAPI *ptrGetScaleFactorForMonitor)(HMONITOR hmonitor, int *scale) = nullptr; 9 | 10 | // DPI.h 11 | int systemDPI = USER_DEFAULT_SCREEN_DPI; 12 | 13 | void initDPI() { 14 | if (HMODULE hShcore = checkLE(LoadLibrary(L"Shcore"))) { 15 | ptrGetScaleFactorForMonitor = (decltype(ptrGetScaleFactorForMonitor)) 16 | checkLE(GetProcAddress(hShcore, "GetScaleFactorForMonitor")); 17 | } 18 | 19 | if (HDC screen = checkLE(GetDC(nullptr))) { 20 | systemDPI = GetDeviceCaps(screen, LOGPIXELSX); 21 | ReleaseDC(nullptr, screen); 22 | } 23 | } 24 | 25 | int monitorDPI(HMONITOR monitor) { 26 | int scale; 27 | if (ptrGetScaleFactorForMonitor && checkHR(ptrGetScaleFactorForMonitor(monitor, &scale))) 28 | return MulDiv(scale, USER_DEFAULT_SCREEN_DPI, 100); 29 | return systemDPI; 30 | } 31 | 32 | int scaleDPI(int dp) { 33 | return MulDiv(dp, systemDPI, USER_DEFAULT_SCREEN_DPI); 34 | } 35 | 36 | SIZE scaleDPI(SIZE size) { 37 | return {scaleDPI(size.cx), scaleDPI(size.cy)}; 38 | } 39 | 40 | POINT scaleDPI(POINT p) { 41 | return {scaleDPI(p.x), scaleDPI(p.y)}; 42 | } 43 | 44 | int invScaleDPI(int px) { 45 | return MulDiv(px, USER_DEFAULT_SCREEN_DPI, systemDPI); 46 | } 47 | 48 | SIZE invScaleDPI(SIZE size) { 49 | return {invScaleDPI(size.cx), invScaleDPI(size.cy)}; 50 | } 51 | 52 | POINT invScaleDPI(POINT p) { 53 | return {invScaleDPI(p.x), invScaleDPI(p.y)}; 54 | } 55 | 56 | POINT pointMulDiv(POINT p, int num, int denom) { 57 | return {MulDiv(p.x, num, denom), MulDiv(p.y, num, denom)}; 58 | } 59 | 60 | SIZE sizeMulDiv(SIZE s, int num, int denom) { 61 | return {MulDiv(s.cx, num, denom), MulDiv(s.cy, num, denom)}; 62 | } 63 | 64 | int pointsToPixels(int pt) { 65 | return MulDiv(pt, systemDPI, 72); 66 | } 67 | 68 | int pixelsToPoints(int px) { 69 | return MulDiv(px, 72, systemDPI); 70 | } 71 | 72 | } // namespace 73 | -------------------------------------------------------------------------------- /src/DPI.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | 6 | namespace chromafiler { 7 | 8 | // modern DPI scaling methods were added in Windows 10 1607, but we are targeting Windows 7 9 | 10 | extern int systemDPI; 11 | 12 | // to be used in SetViewModeAndIconSize (does not scale with DPI) 13 | const int SHELL_SMALL_ICON = 16; 14 | 15 | void initDPI(); 16 | int monitorDPI(HMONITOR monitor); 17 | 18 | int scaleDPI(int dp); 19 | SIZE scaleDPI(SIZE size); 20 | POINT scaleDPI(POINT p); 21 | int invScaleDPI(int px); 22 | SIZE invScaleDPI(SIZE size); 23 | POINT invScaleDPI(POINT p); 24 | POINT pointMulDiv(POINT p, int num, int denom); 25 | SIZE sizeMulDiv(SIZE s, int num, int denom); 26 | 27 | // font 28 | int pointsToPixels(int pt); 29 | int pixelsToPoints(int px); 30 | 31 | } // namespace 32 | -------------------------------------------------------------------------------- /src/ExecuteCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "ExecuteCommand.h" 2 | #include "main.h" 3 | #include "CreateItemWindow.h" 4 | #include "TextWindow.h" 5 | #include "Update.h" 6 | 7 | namespace chromafiler { 8 | 9 | CFExecute::CFExecute(bool text) : text(text) { 10 | lockProcess(); 11 | if (text) { 12 | debugPrintf(L"Invoked for ChromaText\n"); 13 | } 14 | } 15 | 16 | CFExecute::~CFExecute() { 17 | unlockProcess(); 18 | } 19 | 20 | STDMETHODIMP_(ULONG) CFExecute::AddRef() { return UnknownImpl::AddRef(); } 21 | STDMETHODIMP_(ULONG) CFExecute::Release() { return UnknownImpl::Release(); } 22 | 23 | STDMETHODIMP CFExecute::QueryInterface(REFIID id, void **obj) { 24 | static const QITAB interfaces[] = { 25 | QITABENT(CFExecute, IObjectWithSelection), 26 | QITABENT(CFExecute, IExecuteCommand), 27 | QITABENT(CFExecute, IDropTarget), 28 | {}, 29 | }; 30 | HRESULT hr = QISearch(this, interfaces, id, obj); 31 | if (SUCCEEDED(hr)) 32 | return hr; 33 | return UnknownImpl::QueryInterface(id, obj); 34 | } 35 | 36 | /* IObjectWithSelection */ 37 | 38 | STDMETHODIMP CFExecute::SetSelection(IShellItemArray *const array) { 39 | selection = array; 40 | return S_OK; 41 | } 42 | 43 | STDMETHODIMP CFExecute::GetSelection(REFIID id, void **obj) { 44 | if (selection) 45 | return selection->QueryInterface(id, obj); 46 | *obj = nullptr; 47 | return E_NOINTERFACE; 48 | } 49 | 50 | /* IExecuteCommand */ 51 | 52 | STDMETHODIMP CFExecute::SetKeyState(DWORD) { return S_OK; } 53 | STDMETHODIMP CFExecute::SetParameters(const wchar_t *) { return S_OK; } 54 | STDMETHODIMP CFExecute::SetNoShowUI(BOOL) { return S_OK; } 55 | 56 | STDMETHODIMP CFExecute::SetDirectory(const wchar_t *path) { 57 | int size = lstrlen(path) + 1; 58 | workingDir = wstr_ptr(new wchar_t[size]); 59 | CopyMemory(workingDir.get(), path, size * sizeof(wchar_t)); 60 | return S_OK; 61 | } 62 | 63 | STDMETHODIMP CFExecute::SetPosition(POINT point) { 64 | monitor = MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST); 65 | return S_OK; 66 | } 67 | 68 | STDMETHODIMP CFExecute::SetShowWindow(int show) { 69 | showCommand = show; 70 | return S_OK; 71 | } 72 | 73 | STDMETHODIMP CFExecute::Execute() { 74 | debugPrintf(L"Invoked with DelegateExecute\n"); 75 | if (!selection) { 76 | if (!workingDir) 77 | return E_UNEXPECTED; 78 | // this happens when invoked on background 79 | CComPtr item; 80 | if (checkHR(SHCreateItemFromParsingName(workingDir.get(), nullptr, IID_PPV_ARGS(&item)))) { 81 | CComPtr shellWindows; 82 | checkHR(shellWindows.CoCreateInstance(CLSID_ShellWindows)); 83 | openItem(item, shellWindows); 84 | } 85 | } else { 86 | HRESULT hr; 87 | if (FAILED(hr = openArray(selection))) return hr; 88 | } 89 | autoUpdateCheck(); 90 | return S_OK; 91 | } 92 | 93 | /* IDropTarget */ 94 | 95 | STDMETHODIMP CFExecute::DragEnter(IDataObject *, DWORD, POINTL, DWORD *effect) { 96 | *effect &= DROPEFFECT_LINK; 97 | return S_OK; 98 | } 99 | 100 | STDMETHODIMP CFExecute::DragOver(DWORD, POINTL, DWORD *effect) { 101 | *effect &= DROPEFFECT_LINK; 102 | return S_OK; 103 | } 104 | 105 | STDMETHODIMP CFExecute::DragLeave() { 106 | return S_OK; 107 | } 108 | 109 | STDMETHODIMP CFExecute::Drop(IDataObject *const dataObject, DWORD keyState, POINTL pt, 110 | DWORD *effect) { 111 | debugPrintf(L"Invoked with DropTarget\n"); 112 | // https://devblogs.microsoft.com/oldnewthing/20130204-00/?p=5363 113 | SetKeyState(keyState); 114 | SetPosition({pt.x, pt.y}); 115 | HRESULT hr; 116 | CComPtr itemArray; 117 | if (!checkHR(hr = SHCreateShellItemArrayFromDataObject(dataObject, IID_PPV_ARGS(&itemArray)))) 118 | return hr; 119 | if (FAILED(hr = openArray(itemArray))) 120 | return hr; 121 | *effect &= DROPEFFECT_LINK; 122 | autoUpdateCheck(); 123 | return S_OK; 124 | } 125 | 126 | HRESULT CFExecute::openArray(IShellItemArray *const array) { 127 | CComPtr shellWindows; 128 | checkHR(shellWindows.CoCreateInstance(CLSID_ShellWindows)); 129 | 130 | HRESULT hr; 131 | CComPtr enumItems; 132 | if (!checkHR(hr = array->EnumItems(&enumItems))) 133 | return hr; 134 | CComPtr item; 135 | while (enumItems->Next(1, &item, nullptr) == S_OK) { 136 | openItem(item, shellWindows); 137 | item = nullptr; 138 | } 139 | return S_OK; 140 | } 141 | 142 | void CFExecute::openItem(IShellItem *const item, IShellWindows *const shellWindows) { 143 | CComPtr resolved = resolveLink(item); 144 | 145 | if (shellWindows && showItemWindow(resolved, shellWindows, showCommand)) 146 | return; 147 | 148 | CComPtr window; 149 | if (text) { 150 | window.Attach(new TextWindow(nullptr, resolved)); 151 | } else { 152 | window = createItemWindow(nullptr, resolved); 153 | } 154 | window->create(window->requestedRect(monitor), showCommand); 155 | // fix issue when invoking 64-bit ChromaFiler from 32-bit app 156 | if (showCommand == SW_SHOWNORMAL) 157 | window->setForeground(); 158 | } 159 | 160 | /* Factory */ 161 | 162 | CFExecuteFactory::CFExecuteFactory(bool text) : text(text) {} 163 | 164 | STDMETHODIMP_(ULONG) CFExecuteFactory::AddRef() { 165 | return 2; 166 | } 167 | 168 | STDMETHODIMP_(ULONG) CFExecuteFactory::Release() { 169 | return 1; 170 | } 171 | 172 | STDMETHODIMP CFExecuteFactory::QueryInterface(REFIID id, void **obj) { 173 | static const QITAB interfaces[] = { 174 | QITABENT(CFExecuteFactory, IClassFactory), 175 | {}, 176 | }; 177 | return QISearch(this, interfaces, id, obj); 178 | } 179 | 180 | STDMETHODIMP CFExecuteFactory::CreateInstance(IUnknown *const outer, REFIID id, void **obj) { 181 | *obj = nullptr; 182 | if (outer) 183 | return CLASS_E_NOAGGREGATION; 184 | CComPtr ext; 185 | ext.Attach(new CFExecute(text)); 186 | HRESULT hr = ext->QueryInterface(id, obj); 187 | return hr; 188 | } 189 | 190 | STDMETHODIMP CFExecuteFactory::LockServer(BOOL lock) { 191 | if (lock) 192 | lockProcess(); 193 | else 194 | unlockProcess(); 195 | return S_OK; 196 | } 197 | 198 | } // namespace 199 | -------------------------------------------------------------------------------- /src/ExecuteCommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | 4 | #include "COMUtils.h" 5 | #include "ShObjIdl.h" 6 | #include "WinUtils.h" 7 | #include 8 | #include 9 | 10 | namespace chromafiler { 11 | 12 | // https://devblogs.microsoft.com/oldnewthing/20100312-01/?p=14623 13 | // https://devblogs.microsoft.com/oldnewthing/20100503-00/?p=14183 14 | // https://devblogs.microsoft.com/oldnewthing/20100528-01/?p=13883 15 | 16 | // Two CLSIDs for the same class, depending on how it's invoked: 17 | // {87612720-a94e-4fd3-a1f6-b78d7768424f} 18 | const CLSID CLSID_CFExecute = 19 | {0x87612720, 0xa94e, 0x4fd3, {0xa1, 0xf6, 0xb7, 0x8d, 0x77, 0x68, 0x42, 0x4f}}; 20 | // {14c46e3b-9015-4bbb-9f1f-9178a94f856f} 21 | const CLSID CLSID_CFExecuteText = 22 | {0x14c46e3b, 0x9015, 0x4bbb, {0x9f, 0x1f, 0x91, 0x78, 0xa9, 0x4f, 0x85, 0x6f}}; 23 | class CFExecute : public UnknownImpl, public IObjectWithSelection, public IExecuteCommand, 24 | public IDropTarget { 25 | public: 26 | CFExecute(bool text); 27 | ~CFExecute(); 28 | 29 | // IUnknown 30 | STDMETHODIMP_(ULONG) AddRef() override; 31 | STDMETHODIMP_(ULONG) Release() override; 32 | STDMETHODIMP QueryInterface(REFIID id, void **obj) override; 33 | // IObjectWithSelection 34 | STDMETHODIMP SetSelection(IShellItemArray *array) override; 35 | STDMETHODIMP GetSelection(REFIID id, void **obj) override; 36 | // IExecuteCommand 37 | STDMETHODIMP SetKeyState(DWORD) override; 38 | STDMETHODIMP SetParameters(const wchar_t *params) override; 39 | STDMETHODIMP SetPosition(POINT) override; 40 | STDMETHODIMP SetShowWindow(int) override; 41 | STDMETHODIMP SetNoShowUI(BOOL) override; 42 | STDMETHODIMP SetDirectory(const wchar_t *path) override; 43 | STDMETHODIMP Execute() override; 44 | // IDropTarget 45 | STDMETHODIMP DragEnter(IDataObject *dataObject, DWORD keyState, POINTL pt, DWORD *effect) 46 | override; 47 | STDMETHODIMP DragOver(DWORD keyState, POINTL pt, DWORD *effect) override; 48 | STDMETHODIMP DragLeave() override; 49 | STDMETHODIMP Drop(IDataObject *dataObject, DWORD keyState, POINTL pt, DWORD *effect) override; 50 | 51 | private: 52 | HRESULT openArray(IShellItemArray *array); 53 | void openItem(IShellItem *item, IShellWindows *shellWindows); 54 | 55 | const bool text; 56 | CComPtr selection; 57 | HMONITOR monitor = nullptr; 58 | int showCommand = SW_SHOWNORMAL; 59 | wstr_ptr workingDir; 60 | }; 61 | 62 | class CFExecuteFactory : public IClassFactory { 63 | public: 64 | CFExecuteFactory(bool text); 65 | 66 | // IUnknown 67 | STDMETHODIMP_(ULONG) AddRef() override; 68 | STDMETHODIMP_(ULONG) Release() override; 69 | STDMETHODIMP QueryInterface(REFIID id, void **obj) override; 70 | // IClassFactory 71 | STDMETHODIMP CreateInstance(IUnknown *outer, REFIID id, void **obj) override; 72 | STDMETHODIMP LockServer(BOOL lock) override; 73 | 74 | private: 75 | const bool text; 76 | }; 77 | 78 | } // namespace 79 | -------------------------------------------------------------------------------- /src/FolderWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "ItemWindow.h" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace chromafiler { 10 | 11 | class FolderWindow : public ItemWindow, public IServiceProvider, public ICommDlgBrowser2, 12 | public IExplorerBrowserEvents, public IShellFolderViewCB, public IWebBrowserApp { 13 | public: 14 | static void init(); 15 | 16 | FolderWindow(ItemWindow *parent, IShellItem *item); 17 | 18 | bool persistSizeInParent() const override; 19 | 20 | bool handleTopLevelMessage(MSG *msg) override; 21 | 22 | // IUnknown 23 | STDMETHODIMP QueryInterface(REFIID id, void **obj) override; 24 | STDMETHODIMP_(ULONG) AddRef() override; 25 | STDMETHODIMP_(ULONG) Release() override; 26 | // IServiceProvider 27 | STDMETHODIMP QueryService(REFGUID guidService, REFIID riid, void **ppv) override; 28 | // ICommDlgBrowser 29 | STDMETHODIMP OnDefaultCommand(IShellView *view) override; 30 | STDMETHODIMP OnStateChange(IShellView *view, ULONG change) override; 31 | STDMETHODIMP IncludeObject(IShellView *view, PCUITEMID_CHILD pidl) override; 32 | // ICommDlgBrowser2 33 | STDMETHODIMP GetDefaultMenuText(IShellView *view, wchar_t *text, int maxChars) override; 34 | STDMETHODIMP GetViewFlags(DWORD *flags) override; 35 | STDMETHODIMP Notify(IShellView *view, DWORD notifyType) override; 36 | // IExplorerBrowserEvents 37 | STDMETHODIMP OnNavigationPending(PCIDLIST_ABSOLUTE folder) override; 38 | STDMETHODIMP OnNavigationComplete(PCIDLIST_ABSOLUTE folder) override; 39 | STDMETHODIMP OnNavigationFailed(PCIDLIST_ABSOLUTE folder) override; 40 | STDMETHODIMP OnViewCreated(IShellView *shellView) override; 41 | // IShellFolderViewCB 42 | STDMETHODIMP MessageSFVCB(UINT msg, WPARAM wParam, LPARAM lParam) override; 43 | // IDispatch 44 | STDMETHODIMP GetTypeInfoCount(UINT *) override; 45 | STDMETHODIMP GetTypeInfo(UINT, LCID, ITypeInfo **) override; 46 | STDMETHODIMP GetIDsOfNames(REFIID, LPOLESTR *, UINT, LCID, DISPID *) override; 47 | STDMETHODIMP Invoke(DISPID, REFIID, LCID, WORD, DISPPARAMS *, VARIANT *, EXCEPINFO *, UINT *) 48 | override; 49 | // IWebBrowser 50 | STDMETHODIMP GoBack() override; 51 | STDMETHODIMP GoForward() override; 52 | STDMETHODIMP GoHome() override; 53 | STDMETHODIMP GoSearch() override; 54 | STDMETHODIMP Navigate(BSTR, VARIANT *, VARIANT *, VARIANT *, VARIANT *) override; 55 | STDMETHODIMP Refresh() override; 56 | STDMETHODIMP Refresh2(VARIANT *) override; 57 | STDMETHODIMP Stop() override; 58 | STDMETHODIMP get_Application(IDispatch **) override; 59 | STDMETHODIMP get_Parent(IDispatch **) override; 60 | STDMETHODIMP get_Container(IDispatch **) override; 61 | STDMETHODIMP get_Document(IDispatch **) override; 62 | STDMETHODIMP get_TopLevelContainer(VARIANT_BOOL *) override; 63 | STDMETHODIMP get_Type(BSTR *) override; 64 | STDMETHODIMP get_Left(long *) override; 65 | STDMETHODIMP put_Left(long) override; 66 | STDMETHODIMP get_Top(long *) override; 67 | STDMETHODIMP put_Top(long) override; 68 | STDMETHODIMP get_Width(long *) override; 69 | STDMETHODIMP put_Width(long) override; 70 | STDMETHODIMP get_Height(long *) override; 71 | STDMETHODIMP put_Height(long) override; 72 | STDMETHODIMP get_LocationName(BSTR *) override; 73 | STDMETHODIMP get_LocationURL(BSTR *) override; 74 | STDMETHODIMP get_Busy(VARIANT_BOOL *) override; 75 | // IWebBrowserApp 76 | STDMETHODIMP Quit() override; 77 | STDMETHODIMP ClientToWindow(int *, int *) override; 78 | STDMETHODIMP PutProperty(BSTR, VARIANT) override; 79 | STDMETHODIMP GetProperty(BSTR, VARIANT *) override; 80 | STDMETHODIMP get_Name(BSTR *) override; 81 | STDMETHODIMP get_HWND(SHANDLE_PTR *) override; 82 | STDMETHODIMP get_FullName(BSTR *) override; 83 | STDMETHODIMP get_Path(BSTR *) override; 84 | STDMETHODIMP get_Visible(VARIANT_BOOL *) override; 85 | STDMETHODIMP put_Visible(VARIANT_BOOL) override; 86 | STDMETHODIMP get_StatusBar(VARIANT_BOOL *) override; 87 | STDMETHODIMP put_StatusBar(VARIANT_BOOL) override; 88 | STDMETHODIMP get_StatusText(BSTR *) override; 89 | STDMETHODIMP put_StatusText(BSTR) override; 90 | STDMETHODIMP get_ToolBar(int *) override; 91 | STDMETHODIMP put_ToolBar(int) override; 92 | STDMETHODIMP get_MenuBar(VARIANT_BOOL *) override; 93 | STDMETHODIMP put_MenuBar(VARIANT_BOOL) override; 94 | STDMETHODIMP get_FullScreen(VARIANT_BOOL *) override; 95 | STDMETHODIMP put_FullScreen(VARIANT_BOOL) override; 96 | 97 | protected: 98 | enum ViewStateIndex { 99 | STATE_SHELL_VISITED = ItemWindow::STATE_LAST, // 0x8 - folder was visited by shell view 100 | STATE_ICON_POS, // 0x10 101 | STATE_LAST 102 | }; 103 | enum TimerID { 104 | TIMER_UPDATE_SELECTION = 1, 105 | TIMER_LAST 106 | }; 107 | LRESULT handleMessage(UINT message, WPARAM wParam, LPARAM lParam) override; 108 | 109 | static bool useCustomIconPersistence(); 110 | static bool spatialView(IFolderView *folderView); 111 | 112 | bool useDefaultStatusText() const override; 113 | SIZE defaultSize() const override; 114 | bool isFolder() const override; 115 | 116 | void clearViewState(IPropertyBag *bag, uint32_t mask) override; 117 | void writeViewState(IPropertyBag *bag, uint32_t mask) override; 118 | 119 | virtual FOLDERSETTINGS folderSettings() const; 120 | virtual void initDefaultView(IFolderView2 *folderView); 121 | 122 | void onCreate() override; 123 | void onDestroy() override; 124 | bool onCommand(WORD command) override; 125 | LRESULT onDropdown(int command, POINT pos) override; 126 | void onActivate(WORD state, HWND prevWindow) override; 127 | void onSize(SIZE size) override; 128 | 129 | void addToolbarButtons(HWND tb) override; 130 | int getToolbarTooltip(WORD command) override; 131 | 132 | void trackContextMenu(POINT pos) override; 133 | 134 | void onChildDetached() override; 135 | 136 | IDispatch * getShellViewDispatch() override; 137 | void onItemChanged() override; 138 | void refresh() override; 139 | 140 | HWND listView = nullptr; 141 | CComPtr shellView; 142 | 143 | private: 144 | struct ShellViewState { 145 | FOLDERVIEWMODE viewMode = {}; 146 | DWORD flags = 0; 147 | int iconSize = 0; 148 | 149 | UINT numColumns = 0; 150 | std::unique_ptr columns; 151 | std::unique_ptr columnWidths; 152 | 153 | int numSortColumns = 0; 154 | std::unique_ptr sortColumns; 155 | 156 | PROPERTYKEY groupBy = {}; 157 | BOOL groupAscending = false; 158 | 159 | int numItems = 0; 160 | std::unique_ptr[]> itemIds; 161 | std::unique_ptr itemPositions; 162 | }; 163 | 164 | void listViewCreated(); 165 | static LRESULT CALLBACK listViewSubclassProc(HWND hwnd, UINT message, 166 | WPARAM wParam, LPARAM lParam, UINT_PTR subclassID, DWORD_PTR refData); 167 | static LRESULT CALLBACK listViewOwnerProc(HWND hwnd, UINT message, 168 | WPARAM wParam, LPARAM lParam, UINT_PTR subclassID, DWORD_PTR refData); 169 | 170 | static void getViewState(IFolderView2 *folderView, ShellViewState *state); 171 | static void setViewState(IFolderView2 *folderView, const ShellViewState &state); 172 | 173 | bool writeIconPositions(IFolderView *folderView, IStream *stream); 174 | void loadViewState(IPropertyBag *bag); 175 | bool readIconPositions(IFolderView *folderView, IStream *stream); 176 | 177 | void selectionChanged(); 178 | void scheduleUpdateSelection(); 179 | void updateSelection(); 180 | void clearSelection(); 181 | void updateStatus(); 182 | 183 | CComPtr queryBackgroundMenu(HMENU *popupMenu); 184 | void newItem(const char *verb); 185 | void openNewItemMenu(POINT point); 186 | void openViewMenu(POINT point); 187 | void openBackgroundSubMenu(IContextMenu *contextMenu, HMENU subMenu, POINT point); 188 | 189 | CComPtr browser; // will be null if browser can't be initialized! 190 | DWORD eventsCookie = 0; 191 | CComPtr prevCB; 192 | 193 | CComPtr selected; // links are not resolved unlike child->item 194 | 195 | // jank flags 196 | bool ignoreInitialSelection = false; 197 | bool updateSelectionOnActivate = false; 198 | bool activateOnShiftRelease = false; 199 | bool clickActivate = true; 200 | bool firstODDispInfo = false; 201 | bool handlingSetColumnWidth = false; 202 | bool handlingRButtonDown = false; 203 | bool selectedWhileHandlingRButtonDown = false; 204 | bool invokingDefaultVerb = false; 205 | 206 | DWORD clickTime = 0; 207 | POINT clickPos; 208 | 209 | std::unique_ptr storedViewState; 210 | }; 211 | 212 | } // namespace 213 | -------------------------------------------------------------------------------- /src/GDIUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "GDIUtils.h" 2 | #include "GeomUtils.h" 3 | #include 4 | #include 5 | 6 | namespace chromafiler { 7 | 8 | void makeBitmapOpaque(HDC hdc, const RECT &rect) { 9 | // https://devblogs.microsoft.com/oldnewthing/20210915-00/?p=105687 10 | // thank you Raymond Chen :) 11 | BITMAPINFO bitmapInfo = {{sizeof(BITMAPINFOHEADER), 1, 1, 1, 32, BI_RGB}}; 12 | RGBQUAD bitmapBits = { 0x00, 0x00, 0x00, 0xFF }; 13 | StretchDIBits(hdc, rect.left, rect.top, rectWidth(rect), rectHeight(rect), 14 | 0, 0, 1, 1, &bitmapBits, &bitmapInfo, 15 | DIB_RGB_COLORS, SRCPAINT); 16 | } 17 | 18 | HBITMAP iconToPARGB32Bitmap(HICON icon, int width, int height) { 19 | HDC hdcMem = CreateCompatibleDC(nullptr); 20 | BITMAPINFO bitmapInfo = {{sizeof(BITMAPINFOHEADER), width, -height, 1, 32, BI_RGB}}; 21 | HBITMAP bitmap = nullptr; 22 | if ((bitmap = checkLE(CreateDIBSection(hdcMem, &bitmapInfo, DIB_RGB_COLORS, 23 | nullptr, nullptr, 0))) != nullptr) { 24 | SelectBitmap(hdcMem, bitmap); 25 | checkLE(DrawIconEx(hdcMem, 0, 0, icon, width, height, 0, nullptr, DI_NORMAL)); 26 | // TODO convert to premultiplied alpha? 27 | } 28 | DeleteDC(hdcMem); 29 | return bitmap; 30 | } 31 | 32 | void compositeBackground(const BITMAP &bitmap) { 33 | uint8_t *pixels = (uint8_t *)bitmap.bmBits; 34 | int rowIndex = 0; 35 | for (int y = 0; y < bitmap.bmHeight; y++, rowIndex += bitmap.bmWidthBytes) { 36 | int i = rowIndex; 37 | for (int x = 0; x < bitmap.bmWidth; x++, i += 4) { 38 | int alpha = pixels[i + 3]; 39 | if (alpha == 255) 40 | continue; 41 | int c = 255 - alpha; 42 | pixels[i + 0] = (uint8_t)((int)(pixels[i + 0]) * alpha / 255 + c); 43 | pixels[i + 1] = (uint8_t)((int)(pixels[i + 1]) * alpha / 255 + c); 44 | pixels[i + 2] = (uint8_t)((int)(pixels[i + 2]) * alpha / 255 + c); 45 | } 46 | } 47 | } 48 | 49 | } // namespace 50 | -------------------------------------------------------------------------------- /src/GDIUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | 6 | namespace chromafiler { 7 | 8 | void makeBitmapOpaque(HDC hdc, const RECT &rect); 9 | HBITMAP iconToPARGB32Bitmap(HICON icon, int width, int height); 10 | // use alpha channel to composite onto a white background 11 | void compositeBackground(const BITMAP &bitmap); 12 | 13 | } // namespace 14 | -------------------------------------------------------------------------------- /src/GeomUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | namespace chromafiler { 8 | 9 | constexpr int rectWidth(const RECT &rect) { 10 | return rect.right - rect.left; 11 | } 12 | 13 | constexpr int rectHeight(const RECT &rect) { 14 | return rect.bottom - rect.top; 15 | } 16 | 17 | constexpr SIZE rectSize(const RECT &rect) { 18 | return {rectWidth(rect), rectHeight(rect)}; 19 | } 20 | 21 | constexpr bool pointEqual(POINT a, POINT b) { 22 | return a.x == b.x && a.y == b.y; 23 | } 24 | 25 | constexpr bool sizeEqual(SIZE a, SIZE b) { 26 | return a.cx == b.cx && a.cy == b.cy; 27 | } 28 | 29 | constexpr POINT pointFromLParam(LPARAM lp) { 30 | return {GET_X_LPARAM(lp), GET_Y_LPARAM(lp)}; 31 | } 32 | 33 | constexpr SIZE sizeFromLParam(LPARAM lp) { 34 | return {GET_X_LPARAM(lp), GET_Y_LPARAM(lp)}; 35 | } 36 | 37 | } // namespace 38 | -------------------------------------------------------------------------------- /src/ItemWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "COMUtils.h" 5 | #include "ChainWindow.h" 6 | #include "ProxyIcon.h" 7 | #include "SettingsDialog.h" 8 | #include "WinUtils.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace chromafiler { 15 | 16 | class ItemWindow : public WindowImpl, public UnknownImpl { 17 | friend ChainWindow; 18 | friend ProxyIcon; 19 | protected: 20 | static HACCEL accelTable; 21 | static int CAPTION_HEIGHT; 22 | static int cascadeSize(); 23 | 24 | public: 25 | static CComPtr activeWindow; 26 | 27 | static void init(); 28 | static void uninit(); 29 | 30 | static void flashWindow(HWND hwnd); 31 | 32 | ItemWindow(ItemWindow *parent, IShellItem *item); 33 | 34 | virtual SIZE requestedSize(); // called if (! persistSizeInParent()) 35 | virtual RECT requestedRect(HMONITOR preferMonitor); // called for root windows 36 | virtual bool persistSizeInParent() const; 37 | 38 | void setScratch(bool scratch); 39 | void resetViewState(); // call immediately after constructing to reset all view state properties 40 | 41 | bool create(RECT rect, int showCommand); 42 | void close(); 43 | void setForeground(); 44 | 45 | // attempt to relocate item if it has been renamed, moved, or deleted 46 | // return true if item has not changed 47 | bool resolveItem(); 48 | 49 | virtual bool handleTopLevelMessage(MSG *msg); 50 | 51 | CComPtr item; 52 | 53 | protected: 54 | enum ViewStateIndex { 55 | STATE_POS, // 0x1 56 | STATE_SIZE, // 0x2 57 | STATE_CHILD_SIZE, // 0x4 58 | STATE_LAST 59 | }; 60 | enum UserMessage { 61 | // WPARAM: 0, LPARAM: 0 62 | MSG_UPDATE_ICONS = WM_USER, 63 | // WPARAM: 0, LPARAM: 0 64 | MSG_UPDATE_DEFAULT_STATUS_TEXT, 65 | // see SHChangeNotification_Lock 66 | MSG_SHELL_NOTIFY, 67 | // WPARAM: 0, LPARAM: 0 68 | MSG_FLASH_WINDOW, 69 | MSG_LAST 70 | }; 71 | LRESULT handleMessage(UINT message, WPARAM wParam, LPARAM lParam) override; 72 | 73 | virtual SIZE defaultSize() const; 74 | virtual const wchar_t * propBagName() const; 75 | virtual const wchar_t * appUserModelID() const; 76 | virtual bool isFolder() const; 77 | virtual DWORD windowStyle() const; 78 | virtual DWORD windowExStyle() const; 79 | bool useCustomFrame() const override; 80 | // a window that stays open and is not shown in taskbar. currently only used by TrayWindow 81 | virtual bool paletteWindow() const; 82 | virtual bool stickToChild() const; // for windows that override childPos 83 | 84 | virtual bool useDefaultStatusText() const; 85 | virtual SettingsPage settingsStartPage() const; 86 | virtual const wchar_t * helpURL() const; 87 | 88 | virtual void updateWindowPropStore(IPropertyStore *propStore); 89 | static void propStoreWriteString(IPropertyStore *propStore, 90 | const PROPERTYKEY &key, const wchar_t *value); 91 | 92 | CComPtr getPropBag(); 93 | void resetViewState(uint32_t mask); 94 | void persistViewState(); 95 | virtual void clearViewState(IPropertyBag *bag, uint32_t mask); 96 | virtual void writeViewState(IPropertyBag *bag, uint32_t mask); 97 | void viewStateDirty(uint32_t mask); 98 | void viewStateClean(uint32_t mask); 99 | 100 | bool isScratch(); 101 | void onModify(); 102 | 103 | // general window commands 104 | void activate(); 105 | void setRect(RECT rect); 106 | void setPos(POINT pos); 107 | void setSize(SIZE size); 108 | void move(int x, int y); 109 | void adjustSize(int *x, int *y); 110 | virtual RECT windowBody(); 111 | 112 | // message callbacks 113 | virtual void onCreate(); 114 | virtual bool onCloseRequest(); // return false to block close (probably a bad idea) 115 | virtual void onDestroy(); 116 | virtual bool onCommand(WORD command); 117 | virtual LRESULT onDropdown(int command, POINT pos); 118 | virtual bool onControlCommand(HWND controlHwnd, WORD notif); 119 | virtual LRESULT onNotify(NMHDR *nmHdr); 120 | virtual void onActivate(WORD state, HWND prevWindow); 121 | virtual void onSize(SIZE size); 122 | virtual void onPaint(PAINTSTRUCT paint); 123 | 124 | bool hasStatusText(); 125 | void setStatusText(const wchar_t *text); 126 | static TBBUTTON makeToolbarButton(const wchar_t *text, WORD command, BYTE style, 127 | BYTE state = TBSTATE_ENABLED); 128 | void setToolbarButtonState(WORD command, BYTE state); 129 | virtual void addToolbarButtons(HWND tb); 130 | virtual int getToolbarTooltip(WORD command); 131 | 132 | virtual void trackContextMenu(POINT pos); 133 | int trackContextMenu(POINT pos, HMENU menu); // will modify menu! 134 | 135 | void openChild(IShellItem *childItem); 136 | void closeChild(); 137 | virtual void onChildDetached(); 138 | SIZE requestedChildSize(); // called if child->persistSizeInParent() 139 | virtual POINT childPos(SIZE size); 140 | POINT parentPos(SIZE size); 141 | void enableChain(bool enabled); 142 | 143 | virtual IDispatch * getShellViewDispatch(); 144 | void onViewReady(); 145 | virtual void onItemChanged(); 146 | virtual void refresh(); 147 | 148 | void deleteProxy(); 149 | CMINVOKECOMMANDINFOEX makeInvokeInfo(int cmd, POINT point); 150 | 151 | CComHeapPtr title; 152 | 153 | CComPtr parent, child; 154 | 155 | // for handling delayed context menu messages while open (eg. for Open With menu) 156 | CComQIPtr contextMenu2; 157 | CComQIPtr contextMenu3; 158 | 159 | private: 160 | virtual const wchar_t * className() const; 161 | 162 | bool centeredProxy() const; // requires useCustomFrame() == true 163 | 164 | void fakeDragMove(); 165 | void enableTransitions(bool enabled); 166 | void windowRectChanged(); 167 | void autoSizeProxy(LONG width); 168 | LRESULT hitTestNCA(POINT cursor); 169 | 170 | void limitChainWindowRect(RECT *rect); 171 | void openParent(); 172 | void clearParent(); 173 | void detachFromParent(bool closeParent); // updates UI state 174 | void onChildResized(SIZE size); // only called if child->persistSizeInParent() 175 | void detachAndMove(bool closeParent); 176 | 177 | void setChainPreview(); // left-most non-palette window gets the chain preview 178 | 179 | // only (non-palette) windows with no parent OR child are registered 180 | void registerShellWindow(); 181 | void unregisterShellWindow(); 182 | 183 | void registerShellNotify(); 184 | void unregisterShellNotify(); 185 | 186 | void itemMoved(IShellItem *newItem); 187 | 188 | void openParentMenu(); 189 | 190 | void invokeProxyDefaultVerb(); 191 | void openProxyProperties(); 192 | void openProxyContextMenu(); 193 | void proxyDrag(POINT offset); // specify offset from icon origin 194 | void proxyRename(const wchar_t *name); 195 | 196 | CComPtr link; 197 | CComPtr propBag; 198 | bool scratch = false; 199 | uint32_t dirtyViewState = 0; // bit field indexed by ViewStateIndex 200 | 201 | ProxyIcon proxyIcon; 202 | HWND parentToolbar = nullptr, cmdToolbar = nullptr; 203 | HWND statusText = nullptr, statusTooltip = nullptr; 204 | long shellWindowCookie = 0; 205 | ULONG shellNotifyID = 0; 206 | 207 | CComPtr chain; 208 | SIZE childSize = {0, 0}; 209 | POINT moveAccum; 210 | bool firstActivate = false, closing = false; 211 | 212 | SRWLOCK iconLock = SRWLOCK_INIT; 213 | HICON iconLarge = nullptr, iconSmall = nullptr; 214 | 215 | SRWLOCK defaultStatusTextLock = SRWLOCK_INIT; 216 | CComHeapPtr defaultStatusText; 217 | 218 | class IconThread : public StoppableThread { 219 | public: 220 | IconThread(IShellItem *item, ItemWindow *callbackWindow); 221 | protected: 222 | void run() override; 223 | private: 224 | CComHeapPtr itemIDList; 225 | ItemWindow *callbackWindow; 226 | }; 227 | CComPtr iconThread; 228 | 229 | class StatusTextThread : public StoppableThread { 230 | public: 231 | StatusTextThread(IShellItem *item, ItemWindow *callbackWindow); 232 | protected: 233 | void run() override; 234 | private: 235 | CComHeapPtr itemIDList; 236 | ItemWindow *callbackWindow; 237 | }; 238 | CComPtr statusTextThread; 239 | }; 240 | 241 | } // namespace 242 | -------------------------------------------------------------------------------- /src/PreviewHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "PreviewHandler.h" 2 | #include "GeomUtils.h" 3 | 4 | namespace chromafiler { 5 | 6 | PreviewHandlerImpl::~PreviewHandlerImpl() { 7 | if (hwnd) 8 | Unload(); 9 | } 10 | 11 | DWORD PreviewHandlerImpl::windowStyle() const { 12 | return WS_VISIBLE | WS_CHILD; 13 | } 14 | 15 | /* IUnknown */ 16 | 17 | STDMETHODIMP_(ULONG) PreviewHandlerImpl::AddRef() { return UnknownImpl::AddRef(); } 18 | STDMETHODIMP_(ULONG) PreviewHandlerImpl::Release() { return UnknownImpl::Release(); } 19 | 20 | STDMETHODIMP PreviewHandlerImpl::QueryInterface(REFIID id, void **obj) { 21 | static const QITAB interfaces[] = { 22 | QITABENT(PreviewHandlerImpl, IObjectWithSite), 23 | QITABENT(PreviewHandlerImpl, IInitializeWithItem), 24 | QITABENT(PreviewHandlerImpl, IPreviewHandler), 25 | {} 26 | }; 27 | HRESULT hr = QISearch(this, interfaces, id, obj); 28 | if (SUCCEEDED(hr)) 29 | return hr; 30 | return UnknownImpl::QueryInterface(id, obj); 31 | } 32 | 33 | /* IObjectWithSite */ 34 | 35 | STDMETHODIMP PreviewHandlerImpl::SetSite(IUnknown *site) { 36 | frame = site; 37 | return S_OK; 38 | } 39 | 40 | STDMETHODIMP PreviewHandlerImpl::GetSite(REFIID id, void **site) { 41 | if (frame) 42 | return frame->QueryInterface(id, site); 43 | *site = nullptr; 44 | return E_FAIL; 45 | } 46 | 47 | /* IInitializeWithItem */ 48 | 49 | STDMETHODIMP PreviewHandlerImpl::Initialize(IShellItem *initItem, DWORD) { 50 | item = initItem; 51 | return S_OK; 52 | } 53 | 54 | /* IPreviewHandler */ 55 | 56 | STDMETHODIMP PreviewHandlerImpl::SetWindow(HWND newParent, const RECT *rect) { 57 | parent = newParent; 58 | if (hwnd) 59 | checkLE(SetParent(hwnd, parent)); 60 | return SetRect(rect); 61 | } 62 | 63 | STDMETHODIMP PreviewHandlerImpl::SetRect(const RECT *rect) { 64 | area = *rect; 65 | if (hwnd) 66 | MoveWindow(hwnd, area.left, area.top, rectWidth(area), rectHeight(area), TRUE); 67 | return S_OK; 68 | } 69 | 70 | STDMETHODIMP PreviewHandlerImpl::DoPreview() { 71 | CreateWindow(className(), nullptr, windowStyle(), 72 | area.left, area.top, rectWidth(area), rectHeight(area), 73 | parent, nullptr, GetModuleHandle(nullptr), (WindowImpl *)this); 74 | return S_OK; 75 | } 76 | 77 | STDMETHODIMP PreviewHandlerImpl::Unload() { 78 | DestroyWindow(hwnd); 79 | hwnd = nullptr; 80 | return S_OK; 81 | } 82 | 83 | STDMETHODIMP PreviewHandlerImpl::SetFocus() { 84 | return S_OK; 85 | } 86 | 87 | STDMETHODIMP PreviewHandlerImpl::QueryFocus(HWND *focusWnd) { 88 | *focusWnd = GetFocus(); 89 | return S_OK; 90 | } 91 | 92 | STDMETHODIMP PreviewHandlerImpl::TranslateAccelerator(MSG *msg) { 93 | if (frame) 94 | return frame->TranslateAccelerator(msg); // TODO 95 | return S_FALSE; 96 | } 97 | 98 | } // namespace 99 | -------------------------------------------------------------------------------- /src/PreviewHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | 4 | #include 5 | #include 6 | #include "COMUtils.h" 7 | #include "WinUtils.h" 8 | 9 | namespace chromafiler { 10 | 11 | class PreviewHandlerImpl : public WindowImpl, public UnknownImpl, 12 | public IObjectWithSite, public IInitializeWithItem, public IPreviewHandler { 13 | public: 14 | virtual ~PreviewHandlerImpl(); 15 | 16 | // IUnknown 17 | STDMETHODIMP QueryInterface(REFIID id, void **obj) override; 18 | STDMETHODIMP_(ULONG) AddRef() override; 19 | STDMETHODIMP_(ULONG) Release() override; 20 | // IObjectWithSite 21 | STDMETHODIMP SetSite(IUnknown *) override; 22 | STDMETHODIMP GetSite(REFIID, void **) override; 23 | // IInitializeWithItem 24 | STDMETHODIMP Initialize(IShellItem *, DWORD) override; 25 | // IPreviewHandler 26 | STDMETHODIMP SetWindow(HWND, const RECT *) override; 27 | STDMETHODIMP SetRect(const RECT *) override; 28 | STDMETHODIMP DoPreview() override; 29 | STDMETHODIMP Unload() override; 30 | STDMETHODIMP SetFocus() override; 31 | STDMETHODIMP QueryFocus(HWND *hwnd) override; 32 | STDMETHODIMP TranslateAccelerator(MSG *msg) override; 33 | 34 | protected: 35 | virtual const wchar_t * className() const = 0; 36 | virtual DWORD windowStyle() const; 37 | 38 | CComQIPtr frame; 39 | CComPtr item; 40 | 41 | private: 42 | HWND parent = nullptr; 43 | RECT area = {}; 44 | }; 45 | 46 | } // namespace 47 | -------------------------------------------------------------------------------- /src/PreviewWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "PreviewWindow.h" 2 | #include "GeomUtils.h" 3 | #include "WinUtils.h" 4 | #include 5 | #include 6 | 7 | namespace chromafiler { 8 | 9 | // https://geelaw.blog/entries/ipreviewhandlerframe-wpf-2-interop/ 10 | 11 | const wchar_t PREVIEW_CONTAINER_CLASS[] = L"ChromaFiler Preview Container"; 12 | 13 | const int FACTORY_CACHE_SIZE = 4; 14 | 15 | enum WorkerUserMessage { 16 | // WPARAM: 0, LPARAM: InitPreviewRequest (calls free!) 17 | MSG_INIT_PREVIEW_REQUEST = WM_USER, 18 | // WPARAM: 0, LPARAM: IPreviewHandler (marshalled, calls Release!) 19 | MSG_RELEASE_PREVIEW 20 | }; 21 | 22 | struct FactoryCacheEntry { 23 | CLSID clsid = {}; 24 | CComPtr factory; 25 | }; 26 | 27 | HANDLE PreviewWindow::initPreviewThread = nullptr; 28 | // used by worker thread: 29 | static FactoryCacheEntry factoryCache[FACTORY_CACHE_SIZE] = {}; 30 | static int factoryCacheIndex = 0; 31 | 32 | void PreviewWindow::init() { 33 | WNDCLASS containerClass = {}; 34 | containerClass.lpszClassName = PREVIEW_CONTAINER_CLASS; 35 | containerClass.lpfnWndProc = DefWindowProc; 36 | containerClass.hInstance = GetModuleHandle(nullptr); 37 | containerClass.hCursor = LoadCursor(nullptr, IDC_ARROW); 38 | RegisterClass(&containerClass); 39 | 40 | SHCreateThreadWithHandle(initPreviewThreadProc, nullptr, CTF_COINIT_STA, nullptr, 41 | &initPreviewThread); 42 | } 43 | 44 | void PreviewWindow::uninit() { 45 | if (initPreviewThread) { 46 | checkLE(PostThreadMessage(GetThreadId(initPreviewThread), WM_QUIT, 0, 0)); 47 | // Wait for thread to exit 48 | // Avoid deadlock when destroying objects created on the main thread 49 | DWORD res; 50 | do { 51 | res = MsgWaitForMultipleObjects(1, &initPreviewThread, FALSE, INFINITE, QS_ALLINPUT); 52 | if (res == WAIT_OBJECT_0 + 1) { 53 | MSG msg; 54 | while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { 55 | TranslateMessage(&msg); 56 | DispatchMessage(&msg); 57 | } 58 | } 59 | } while (res != WAIT_OBJECT_0 && res != WAIT_FAILED); 60 | checkLE(CloseHandle(initPreviewThread)); 61 | } 62 | for (auto &entry : factoryCache) 63 | entry.factory.Release(); 64 | } 65 | 66 | PreviewWindow::PreviewWindow(ItemWindow *const parent, IShellItem *const item, 67 | CLSID previewID, bool async) 68 | : ItemWindow(parent, item), 69 | previewID(previewID), 70 | async(async) {} 71 | 72 | void PreviewWindow::onCreate() { 73 | ItemWindow::onCreate(); 74 | 75 | RECT previewRect = windowBody(); 76 | previewRect.bottom += CAPTION_HEIGHT; // initial rect is wrong 77 | if (async) { 78 | // some preview handlers don't respect the given rect and always fill their window 79 | // so wrap the preview handler in a container window 80 | container = checkLE(CreateWindow(PREVIEW_CONTAINER_CLASS, nullptr, WS_VISIBLE | WS_CHILD, 81 | previewRect.left, previewRect.top, rectWidth(previewRect), rectHeight(previewRect), 82 | hwnd, nullptr, GetWindowInstance(hwnd), nullptr)); 83 | } 84 | 85 | requestPreview(container ? clientRect(container) : previewRect); 86 | } 87 | 88 | void PreviewWindow::onDestroy() { 89 | ItemWindow::onDestroy(); 90 | destroyPreview(); 91 | initRequest->cancel(); 92 | } 93 | 94 | void PreviewWindow::onActivate(WORD state, HWND prevWindow) { 95 | ItemWindow::onActivate(state, prevWindow); 96 | if (state != WA_INACTIVE) { 97 | // when clicking in the preview window this produces an RPC_E_CANTCALLOUT_ININPUTSYNCCALL 98 | // error, but it seems harmless 99 | if (preview) 100 | preview->SetFocus(); 101 | } 102 | } 103 | 104 | void PreviewWindow::onSize(SIZE size) { 105 | ItemWindow::onSize(size); 106 | RECT previewRect = windowBody(); 107 | if (container) { 108 | MoveWindow(container, previewRect.left, previewRect.top, 109 | rectWidth(previewRect), rectHeight(previewRect), TRUE); 110 | previewRect = {0, 0, rectWidth(previewRect), rectHeight(previewRect)}; 111 | } 112 | if (preview) 113 | checkHR(preview->SetRect(&previewRect)); 114 | } 115 | 116 | LRESULT PreviewWindow::handleMessage(UINT message, WPARAM wParam, LPARAM lParam) { 117 | if (message == MSG_INIT_PREVIEW_COMPLETE) { 118 | AcquireSRWLockExclusive(&previewStreamLock); 119 | CComPtr newPreviewStream = previewStream; 120 | previewStream = nullptr; 121 | ReleaseSRWLockExclusive(&previewStreamLock); 122 | if (!newPreviewStream) 123 | return 0; 124 | 125 | destroyPreview(); 126 | if (!checkHR(CoUnmarshalInterface(newPreviewStream, IID_PPV_ARGS(&preview)))) 127 | return 0; 128 | checkHR(IUnknown_SetSite(preview, (IPreviewHandlerFrame *)this)); 129 | checkHR(preview->DoPreview()); 130 | RECT previewRect = container ? clientRect(container) : windowBody(); 131 | // required for some preview handlers to render correctly initially (eg. SumatraPDF) 132 | checkHR(preview->SetRect(&previewRect)); 133 | return 0; 134 | } 135 | return ItemWindow::handleMessage(message, wParam, lParam); 136 | } 137 | 138 | void PreviewWindow::requestPreview(RECT rect) { 139 | initRequest.Attach(new InitPreviewRequest(item, previewID, this, 140 | container ? container : hwnd, rect)); 141 | if (async && initPreviewThread) { 142 | (*initRequest).AddRef(); // keep alive 143 | checkLE(PostThreadMessage(GetThreadId(initPreviewThread), 144 | MSG_INIT_PREVIEW_REQUEST, 0, (LPARAM)&*initRequest)); 145 | } else { 146 | initPreview(initRequest, false); 147 | } 148 | } 149 | 150 | void PreviewWindow::destroyPreview() { 151 | if (preview) { 152 | checkHR(IUnknown_SetSite(preview, nullptr)); 153 | checkHR(preview->Unload()); 154 | 155 | // Windows Media Player doesn't like if you delete another IPreviewHandler between 156 | // initializing and calling SetWindow. So ensure that preview handlers are deleted 157 | // synchronously with the worker thread! 158 | if (async && initPreviewThread) { 159 | CComPtr previewHandlerStream; // no CComPtr 160 | checkHR(CoMarshalInterThreadInterfaceInStream(__uuidof(IPreviewHandler), preview, 161 | &previewHandlerStream)); 162 | checkLE(PostThreadMessage(GetThreadId(initPreviewThread), 163 | MSG_RELEASE_PREVIEW, 0, (LPARAM)previewHandlerStream.Detach())); 164 | CHROMAFILER_MEMLEAK_ALLOC; 165 | } 166 | 167 | preview = nullptr; 168 | } 169 | } 170 | 171 | void PreviewWindow::refresh() { 172 | ItemWindow::refresh(); 173 | initRequest->cancel(); 174 | requestPreview(container ? clientRect(container) : windowBody()); 175 | } 176 | 177 | void PreviewWindow::onItemChanged() { 178 | ItemWindow::onItemChanged(); 179 | initRequest->cancel(); 180 | requestPreview(container ? clientRect(container) : windowBody()); 181 | } 182 | 183 | /* IUnknown */ 184 | 185 | STDMETHODIMP PreviewWindow::QueryInterface(REFIID id, void **obj) { 186 | static const QITAB interfaces[] = { 187 | QITABENT(PreviewWindow, IPreviewHandlerFrame), 188 | {}, 189 | }; 190 | HRESULT hr = QISearch(this, interfaces, id, obj); 191 | if (SUCCEEDED(hr)) 192 | return hr; 193 | return ItemWindow::QueryInterface(id, obj); 194 | } 195 | 196 | STDMETHODIMP_(ULONG) PreviewWindow::AddRef() { 197 | return ItemWindow::AddRef(); 198 | } 199 | 200 | STDMETHODIMP_(ULONG) PreviewWindow::Release() { 201 | return ItemWindow::Release(); 202 | } 203 | 204 | /* IPreviewHandlerFrame */ 205 | 206 | STDMETHODIMP PreviewWindow::GetWindowContext(PREVIEWHANDLERFRAMEINFO *info) { 207 | // fixes shortcuts in eg. Excel handler 208 | info->cAccelEntries = CopyAcceleratorTable(accelTable, nullptr, 0); 209 | info->haccel = accelTable; 210 | return S_OK; 211 | } 212 | 213 | STDMETHODIMP PreviewWindow::TranslateAccelerator(MSG *msg) { 214 | // fixes shortcuts in eg. windows Mime handler (.mht) 215 | return handleTopLevelMessage(msg) ? S_OK : S_FALSE; 216 | } 217 | 218 | 219 | PreviewWindow::InitPreviewRequest::InitPreviewRequest(IShellItem *const item, CLSID previewID, 220 | PreviewWindow *const callbackWindow, HWND parent, RECT rect) 221 | : previewID(previewID), 222 | callbackWindow(callbackWindow), 223 | parent(parent), 224 | rect(rect) { 225 | checkHR(SHGetIDListFromObject(item, &itemIDList)); 226 | cancelEvent = checkLE(CreateEvent(nullptr, TRUE, FALSE, nullptr)); 227 | } 228 | 229 | PreviewWindow::InitPreviewRequest::~InitPreviewRequest() { 230 | checkLE(CloseHandle(cancelEvent)); 231 | } 232 | 233 | void PreviewWindow::InitPreviewRequest::cancel() { 234 | AcquireSRWLockExclusive(&cancelLock); 235 | checkLE(SetEvent(cancelEvent)); 236 | ReleaseSRWLockExclusive(&cancelLock); 237 | } 238 | 239 | DWORD WINAPI PreviewWindow::initPreviewThreadProc(void *) { 240 | MSG msg; 241 | while (GetMessage(&msg, nullptr, 0, 0)) { 242 | if (msg.hwnd == nullptr && msg.message == MSG_INIT_PREVIEW_REQUEST) { 243 | CComPtr request; 244 | request.Attach((InitPreviewRequest *)msg.lParam); 245 | initPreview(request, true); 246 | } else if (msg.hwnd == nullptr && msg.message == MSG_RELEASE_PREVIEW) { 247 | CComPtr preview; 248 | checkHR(CoGetInterfaceAndReleaseStream((IStream*)msg.lParam, IID_PPV_ARGS(&preview))); 249 | CHROMAFILER_MEMLEAK_FREE; // and immediately goes out of scope 250 | } else { 251 | // regular message loop is required by some preview handlers (eg. Windows Mime handler) 252 | TranslateMessage(&msg); 253 | DispatchMessage(&msg); 254 | } 255 | } 256 | return 0; 257 | } 258 | 259 | void PreviewWindow::initPreview(InitPreviewRequest *const request, bool async) { 260 | CComPtr item; 261 | if (!checkHR(SHCreateItemFromIDList(request->itemIDList, IID_PPV_ARGS(&item)))) 262 | return; 263 | request->itemIDList.Free(); 264 | 265 | CComPtr preview; 266 | for (auto &entry : factoryCache) { 267 | if (entry.clsid == request->previewID && entry.factory) { 268 | debugPrintf(L"Found cached factory\n"); 269 | if (!checkHR(entry.factory->CreateInstance(nullptr, IID_PPV_ARGS(&preview)))) { 270 | entry.clsid = {}; 271 | entry.factory = nullptr; 272 | } 273 | } 274 | } 275 | if (!preview) { 276 | CComPtr factory; 277 | if (!checkHR(CoGetClassObject(request->previewID, CLSCTX_LOCAL_SERVER, nullptr, 278 | IID_PPV_ARGS(&factory)))) 279 | return; 280 | if (!checkHR(factory->CreateInstance(nullptr, IID_PPV_ARGS(&preview)))) 281 | return; 282 | if (async) { 283 | // https://stackoverflow.com/a/5002596/11525734 284 | factoryCache[factoryCacheIndex] = {request->previewID, factory}; 285 | factoryCacheIndex = (factoryCacheIndex + 1) % FACTORY_CACHE_SIZE; 286 | } 287 | } 288 | 289 | if (WaitForSingleObject(request->cancelEvent, 0) == WAIT_OBJECT_0) 290 | return; // early exit 291 | if (!initPreviewWithItem(preview, item)) 292 | return; // not initialized yet, no need to call Unload() 293 | 294 | CComQIPtr visuals(preview); 295 | if (visuals) { 296 | checkHR(visuals->SetBackgroundColor(GetSysColor(COLOR_WINDOW))); 297 | visuals->SetTextColor(GetSysColor(COLOR_WINDOWTEXT)); // may not be implemented 298 | } 299 | 300 | CComPtr previewHandlerStream; 301 | checkHR(CoMarshalInterThreadInterfaceInStream(__uuidof(IPreviewHandler), preview, 302 | &previewHandlerStream)); 303 | 304 | // ensure the window is not closed before the message is posted 305 | AcquireSRWLockExclusive(&request->cancelLock); 306 | if (WaitForSingleObject(request->cancelEvent, 0) == WAIT_OBJECT_0) { 307 | ReleaseSRWLockExclusive(&request->cancelLock); 308 | checkHR(preview->Unload()); 309 | return; // early exit 310 | } 311 | 312 | checkHR(preview->SetWindow(request->parent, &request->rect)); 313 | 314 | AcquireSRWLockExclusive(&request->callbackWindow->previewStreamLock); 315 | request->callbackWindow->previewStream = previewHandlerStream; 316 | ReleaseSRWLockExclusive(&request->callbackWindow->previewStreamLock); 317 | PostMessage(request->callbackWindow->hwnd, MSG_INIT_PREVIEW_COMPLETE, 0, 0); 318 | 319 | ReleaseSRWLockExclusive(&request->cancelLock); 320 | } 321 | 322 | bool PreviewWindow::initPreviewWithItem(IPreviewHandler *const preview, IShellItem *const item) { 323 | CComPtr context; 324 | if (checkHR(CreateBindCtx(0, &context))) { 325 | BIND_OPTS options = {sizeof(BIND_OPTS), 0, STGM_READ | STGM_SHARE_DENY_NONE, 0}; 326 | checkHR(context->SetBindOptions(&options)); 327 | } 328 | // try using stream first, it will be more secure by limiting file operations 329 | CComPtr stream; 330 | if (checkHR(item->BindToHandler(context, BHID_Stream, IID_PPV_ARGS(&stream)))) { 331 | CComQIPtr streamInit(preview); 332 | if (streamInit) { 333 | if (checkHR(streamInit->Initialize(stream, STGM_READ))) { 334 | debugPrintf(L"Init with stream\n"); 335 | return true; 336 | } 337 | } 338 | } 339 | 340 | CComQIPtr itemInit(preview); 341 | if (itemInit) { 342 | if (checkHR(itemInit->Initialize(item, STGM_READ))) { 343 | debugPrintf(L"Init with item\n"); 344 | return true; 345 | } 346 | } 347 | 348 | CComHeapPtr path; 349 | if (checkHR(item->GetDisplayName(SIGDN_FILESYSPATH, &path))) { 350 | CComQIPtr fileInit(preview); 351 | if (fileInit) { 352 | if (checkHR(fileInit->Initialize(path, STGM_READ))) { 353 | debugPrintf(L"Init with path %s\n", &*path); 354 | return true; 355 | } 356 | } 357 | } 358 | return false; 359 | } 360 | 361 | } // namespace 362 | -------------------------------------------------------------------------------- /src/PreviewWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "ItemWindow.h" 5 | 6 | namespace chromafiler { 7 | 8 | class PreviewWindow : public ItemWindow, public IPreviewHandlerFrame { 9 | 10 | struct InitPreviewRequest : public UnknownImpl { 11 | InitPreviewRequest(IShellItem *item, CLSID previewID, 12 | PreviewWindow *callbackWindow, HWND parent, RECT rect); 13 | ~InitPreviewRequest(); 14 | void cancel(); // ok to call this multiple times 15 | 16 | CComHeapPtr itemIDList; 17 | const CLSID previewID; 18 | PreviewWindow *const callbackWindow; 19 | const HWND parent; 20 | const RECT rect; 21 | HANDLE cancelEvent; 22 | SRWLOCK cancelLock = SRWLOCK_INIT; 23 | }; 24 | 25 | public: 26 | static void init(); 27 | static void uninit(); 28 | 29 | PreviewWindow(ItemWindow *parent, IShellItem *item, CLSID previewID, bool async = true); 30 | 31 | // IUnknown 32 | STDMETHODIMP QueryInterface(REFIID id, void **obj) override; 33 | STDMETHODIMP_(ULONG) AddRef() override; 34 | STDMETHODIMP_(ULONG) Release() override; 35 | // IPreviewHandlerFrame 36 | STDMETHODIMP GetWindowContext(PREVIEWHANDLERFRAMEINFO *info); 37 | STDMETHODIMP TranslateAccelerator(MSG *msg); 38 | 39 | protected: 40 | enum UserMessage { 41 | // WPARAM: 0, LPARAM: 0 42 | MSG_INIT_PREVIEW_COMPLETE = ItemWindow::MSG_LAST, 43 | MSG_LAST 44 | }; 45 | LRESULT handleMessage(UINT message, WPARAM wParam, LPARAM lParam) override; 46 | 47 | void onCreate() override; 48 | void onDestroy() override; 49 | void onActivate(WORD state, HWND prevWindow) override; 50 | void onSize(SIZE size) override; 51 | 52 | void refresh() override; 53 | void onItemChanged() override; 54 | 55 | private: 56 | void requestPreview(RECT rect); 57 | void destroyPreview(); 58 | 59 | const bool async; 60 | const CLSID previewID; 61 | CComPtr initRequest; 62 | CComPtr preview; // will be null if preview can't be loaded! 63 | HWND container = nullptr; 64 | 65 | SRWLOCK previewStreamLock = SRWLOCK_INIT; 66 | CComPtr previewStream; 67 | 68 | // worker thread 69 | static HANDLE initPreviewThread; 70 | static DWORD WINAPI initPreviewThreadProc(void *); 71 | static void initPreview(InitPreviewRequest *request, bool async); 72 | static bool initPreviewWithItem(IPreviewHandler *preview, IShellItem *item); 73 | }; 74 | 75 | } // namespace 76 | -------------------------------------------------------------------------------- /src/ProxyIcon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace chromafiler { 10 | 11 | class ItemWindow; 12 | 13 | class ProxyIcon : public IDropSource, public IDropTarget { 14 | public: 15 | static void init(); 16 | static void initTheme(HTHEME theme); 17 | static void initMetrics(const NONCLIENTMETRICS &metrics); 18 | static void uninit(); 19 | 20 | ProxyIcon(ItemWindow *outer); 21 | 22 | void create(HWND parent, IShellItem *item, wchar_t *title, int top, int height); 23 | void destroy(); // may be called without create() 24 | 25 | bool isToolbarWindow(HWND hwnd) const; 26 | POINT getMenuPoint(HWND parent); 27 | 28 | void setItem(IShellItem *item); 29 | void setTitle(wchar_t *title); 30 | void setIcon(HICON icon); // does not take ownership! 31 | void setActive(bool active); 32 | void setPressedState(bool pressed); 33 | void autoSize(LONG parentWidth, LONG captionLeft, LONG captionRight); 34 | void redrawToolbar(); 35 | 36 | void dragDrop(IDataObject *dataObject, POINT offset); 37 | void beginRename(); 38 | bool isRenaming(); 39 | 40 | bool onControlCommand(HWND controlHwnd, WORD notif); 41 | LRESULT onNotify(NMHDR *nmHdr); 42 | void onThemeChanged(); 43 | 44 | // IUnknown 45 | STDMETHODIMP QueryInterface(REFIID id, void **obj) override; 46 | STDMETHODIMP_(ULONG) AddRef() override; 47 | STDMETHODIMP_(ULONG) Release() override; 48 | // IDropSource 49 | STDMETHODIMP QueryContinueDrag(BOOL escapePressed, DWORD keyState) override; 50 | STDMETHODIMP GiveFeedback(DWORD effect) override; 51 | // IDropTarget 52 | STDMETHODIMP DragEnter(IDataObject *dataObject, DWORD keyState, POINTL pt, DWORD *effect) 53 | override; 54 | STDMETHODIMP DragLeave() override; 55 | STDMETHODIMP DragOver(DWORD keyState, POINTL pt, DWORD *effect) override; 56 | STDMETHODIMP Drop(IDataObject *dataObject, DWORD keyState, POINTL pt, DWORD *effect) override; 57 | 58 | private: 59 | RECT titleRect(); 60 | 61 | void completeRename(); 62 | void cancelRename(); 63 | 64 | // window subclasses 65 | static LRESULT CALLBACK renameBoxProc(HWND hwnd, UINT message, 66 | WPARAM wParam, LPARAM lParam, UINT_PTR subclassID, DWORD_PTR refData); 67 | 68 | ItemWindow * const outer; 69 | 70 | HWND toolbar = nullptr, tooltip = nullptr, renameBox = nullptr; 71 | HIMAGELIST imageList = nullptr; 72 | CComPtr dropTarget; 73 | 74 | bool dragging = false; 75 | }; 76 | 77 | } // namespace 78 | -------------------------------------------------------------------------------- /src/Settings.cpp: -------------------------------------------------------------------------------- 1 | #include "Settings.h" 2 | #include 3 | #include "UIStrings.h" 4 | 5 | namespace chromafiler { 6 | namespace settings { 7 | 8 | bool testMode = false; 9 | 10 | const wchar_t KEY_SETTINGS_NORMAL[] = L"Software\\ChromaFiler"; 11 | const wchar_t KEY_SETTINGS_TEST[] = L"Software\\ChromaFiler\\Test"; 12 | #ifdef CHROMAFILER_DEBUG 13 | #define KEY_SETTINGS (testMode ? KEY_SETTINGS_TEST : KEY_SETTINGS_NORMAL) 14 | #else 15 | #define KEY_SETTINGS KEY_SETTINGS_NORMAL 16 | #endif 17 | 18 | const wchar_t KEY_STARTUP[] = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"; 19 | const wchar_t VAL_STARTUP[] = L"ChromaFiler"; 20 | const wchar_t KEY_DIRECTORY_VERB[] = L"Directory\\shell\\chromafiler"; 21 | const wchar_t KEY_DIRECTORY_BROWSER[] = L"Software\\Classes\\Directory\\Shell"; 22 | const wchar_t KEY_COMPRESSED_BROWSER[] = L"Software\\Classes\\CompressedFolder\\Shell"; 23 | const wchar_t KEY_DRIVE_BROWSER[] = L"Software\\Classes\\Drive\\Shell"; 24 | const wchar_t DATA_BROWSER_SET[] = L"chromafiler"; 25 | const wchar_t DATA_BROWSER_CLEAR[] = L"none"; 26 | 27 | // type should be a RRF_RT_* constant 28 | // data should already contain default value 29 | LSTATUS getSettingsValue(const wchar_t *name, DWORD type, void *data, DWORD size) { 30 | return RegGetValue(HKEY_CURRENT_USER, KEY_SETTINGS, name, type, nullptr, data, &size); 31 | } 32 | 33 | // type should be a REG_* constant (unlike getSettingsValue!) 34 | LSTATUS setSettingsValue(const wchar_t *name, DWORD type, const void *data, DWORD size) { 35 | return RegSetKeyValue(HKEY_CURRENT_USER, KEY_SETTINGS, name, type, data, size); 36 | } 37 | 38 | wstr_ptr getSettingsString(const wchar_t *name, DWORD type, const wchar_t *defaultValue) { 39 | DWORD size; 40 | if (!RegGetValue(HKEY_CURRENT_USER, KEY_SETTINGS, name, type, nullptr, nullptr, &size)) { 41 | wstr_ptr buffer(new wchar_t[size / sizeof(wchar_t)]); 42 | RegGetValue(HKEY_CURRENT_USER, KEY_SETTINGS, name, type, nullptr, buffer.get(), &size); 43 | return buffer; 44 | } else { 45 | DWORD count = lstrlen(defaultValue) + 1; 46 | wstr_ptr buffer(new wchar_t[count]); 47 | CopyMemory(buffer.get(), defaultValue, count * sizeof(wchar_t)); 48 | return buffer; 49 | } 50 | } 51 | 52 | void setSettingsString(const wchar_t *name, DWORD type, const wchar_t *value) { 53 | setSettingsValue(name, type, value, (lstrlen(value) + 1) * sizeof(wchar_t)); 54 | } 55 | 56 | #define SETTINGS_DWORD_VALUE(funcName, type, valueName, defaultValue) \ 57 | type get##funcName() { \ 58 | DWORD value = (defaultValue); \ 59 | getSettingsValue((valueName), RRF_RT_DWORD, &value, sizeof(value)); \ 60 | return (type)value; \ 61 | } \ 62 | void set##funcName(type value) { \ 63 | DWORD dwValue = (DWORD)value; \ 64 | setSettingsValue((valueName), REG_DWORD, &dwValue, sizeof(dwValue)); \ 65 | } 66 | 67 | #define SETTINGS_QWORD_VALUE(funcName, type, valueName, defaultValue) \ 68 | type get##funcName() { \ 69 | ULONGLONG value = (defaultValue); \ 70 | getSettingsValue((valueName), RRF_RT_QWORD, &value, sizeof(value)); \ 71 | return (type)value; \ 72 | } \ 73 | void set##funcName(type value) { \ 74 | ULONGLONG dwValue = (ULONGLONG)value; \ 75 | setSettingsValue((valueName), REG_QWORD, &dwValue, sizeof(dwValue)); \ 76 | } 77 | 78 | #define SETTINGS_BOOL_VALUE(funcName, valueName, defaultValue) \ 79 | SETTINGS_DWORD_VALUE(funcName, bool, valueName, defaultValue) 80 | 81 | #define SETTINGS_SIZE_VALUE(funcName, valueName, defaultValue) \ 82 | SIZE get##funcName() { \ 83 | SIZE value = (defaultValue); \ 84 | getSettingsValue(valueName L"Width", RRF_RT_DWORD, &value.cx, sizeof(value.cx)); \ 85 | getSettingsValue(valueName L"Height", RRF_RT_DWORD, &value.cy, sizeof(value.cy)); \ 86 | return value; \ 87 | } \ 88 | void set##funcName(SIZE value) { \ 89 | setSettingsValue(valueName L"Width", REG_DWORD, &value.cx, sizeof(value.cx)); \ 90 | setSettingsValue(valueName L"Height", REG_DWORD, &value.cy, sizeof(value.cy)); \ 91 | } 92 | 93 | #define SETTINGS_POINT_VALUE(funcName, valueName, defaultValue) \ 94 | POINT get##funcName() { \ 95 | POINT value = (defaultValue); \ 96 | getSettingsValue(valueName L"X", RRF_RT_DWORD, &value.x, sizeof(value.x)); \ 97 | getSettingsValue(valueName L"Y", RRF_RT_DWORD, &value.y, sizeof(value.y)); \ 98 | return value; \ 99 | } \ 100 | void set##funcName(POINT value) { \ 101 | setSettingsValue(valueName L"X", REG_DWORD, &value.x, sizeof(value.x)); \ 102 | setSettingsValue(valueName L"Y", REG_DWORD, &value.y, sizeof(value.y)); \ 103 | } 104 | 105 | #define SETTINGS_STRING_VALUE(funcName, regType, valueName, defaultValue) \ 106 | wstr_ptr get##funcName() { \ 107 | return getSettingsString((valueName), RRF_RT_REG_SZ, (defaultValue)); \ 108 | } \ 109 | void set##funcName(wchar_t *value) { \ 110 | setSettingsString((valueName), (regType), value); \ 111 | } 112 | 113 | #define SETTINGS_FONT_VALUE(funcName, valueName, defaultValue) \ 114 | LOGFONT get##funcName() { \ 115 | LOGFONT value = (defaultValue); \ 116 | getSettingsValue( /* NOT getSettingsString since lfFaceName has a fixed size */ \ 117 | valueName L"Face", RRF_RT_REG_SZ, value.lfFaceName, sizeof(value.lfFaceName)); \ 118 | getSettingsValue(valueName L"Size", REG_DWORD, &value.lfHeight, sizeof(value.lfHeight)); \ 119 | getSettingsValue(valueName L"Weight", REG_DWORD, &value.lfWeight, sizeof(value.lfWeight)); \ 120 | DWORD italic = value.lfItalic; \ 121 | getSettingsValue(valueName L"Italic", REG_DWORD, &italic, sizeof(italic)); \ 122 | value.lfItalic = (BYTE)italic; \ 123 | return value; \ 124 | } \ 125 | void set##funcName(const LOGFONT &value) { \ 126 | setSettingsString(valueName L"Face", REG_SZ, value.lfFaceName); \ 127 | setSettingsValue(valueName L"Size", REG_DWORD, &value.lfHeight, sizeof(value.lfHeight)); \ 128 | setSettingsValue(valueName L"Weight", REG_DWORD, &value.lfWeight, sizeof(value.lfWeight)); \ 129 | DWORD italic = value.lfItalic; \ 130 | setSettingsValue(valueName L"Italic", REG_DWORD, &italic, sizeof(italic)); \ 131 | } 132 | 133 | 134 | SETTINGS_DWORD_VALUE(LastOpenedVersion, DWORD, L"LastOpenedVersion", DEFAULT_LAST_OPENED_VERSION) 135 | SETTINGS_BOOL_VALUE(UpdateCheckEnabled, L"UpdateCheckEnabled", DEFAULT_UPDATE_CHECK_ENABLED); 136 | SETTINGS_QWORD_VALUE(LastUpdateCheck, LONGLONG, L"LastUpdateCheck", DEFAULT_LAST_UPDATE_CHECK) 137 | SETTINGS_QWORD_VALUE(UpdateCheckRate, LONGLONG, L"UpdateCheckRate", DEFAULT_UPDATE_CHECK_RATE) 138 | 139 | SETTINGS_BOOL_VALUE(AdminWarning, L"AdminWarning", DEFAULT_ADMIN_WARNING); 140 | 141 | SETTINGS_STRING_VALUE(StartingFolder, REG_EXPAND_SZ, L"StartingFolder", DEFAULT_STARTING_FOLDER) 142 | SETTINGS_STRING_VALUE(ScratchFolder, REG_EXPAND_SZ, L"ScratchFolder", DEFAULT_SCRATCH_FOLDER) 143 | SETTINGS_STRING_VALUE(ScratchFileName, REG_SZ, L"ScratchFileName", DEFAULT_SCRATCH_FILE_NAME) 144 | 145 | SETTINGS_SIZE_VALUE(ItemWindowSize, L"ItemWindow2", DEFAULT_ITEM_WINDOW_SIZE) 146 | SETTINGS_SIZE_VALUE(FolderWindowSize, L"FolderWindow2", DEFAULT_FOLDER_WINDOW_SIZE) 147 | 148 | SETTINGS_BOOL_VALUE(StatusTextEnabled, L"StatusTextEnabled", DEFAULT_STATUS_TEXT_ENABLED) 149 | SETTINGS_BOOL_VALUE(ToolbarEnabled, L"ToolbarEnabled", DEFAULT_TOOLBAR_ENABLED) 150 | 151 | SETTINGS_BOOL_VALUE(PreviewsEnabled, L"PreviewsEnabled", DEFAULT_PREVIEWS_ENABLED) 152 | 153 | SETTINGS_DWORD_VALUE(OpenSelectionTime, UINT, L"OpenSelectionTime", DEFAULT_OPEN_SELECTION_TIME) 154 | SETTINGS_BOOL_VALUE(DeselectOnOpen, L"DeselectOnOpen", DEFAULT_DESELECT_ON_OPEN) 155 | 156 | SETTINGS_BOOL_VALUE(TextEditorEnabled, L"TextEditorEnabled2", DEFAULT_TEXT_EDITOR_ENABLED) 157 | SETTINGS_FONT_VALUE(TextFont, L"TextFont", DEFAULT_TEXT_FONT) 158 | SETTINGS_DWORD_VALUE(TextTabWidth, int, L"TextTabWidth", DEFAULT_TEXT_TAB_WIDTH) 159 | SETTINGS_BOOL_VALUE(TextWrap, L"TextWrap", DEFAULT_TEXT_WRAP) 160 | SETTINGS_BOOL_VALUE(TextAutoIndent, L"TextAutoIndent", DEFAULT_TEXT_AUTO_INDENT) 161 | SETTINGS_DWORD_VALUE(TextDefaultEncoding, TextEncoding, 162 | L"TextDefaultEncoding", DEFAULT_TEXT_ENCODING) 163 | SETTINGS_BOOL_VALUE(TextAutoEncoding, L"TextAutoEncoding", DEFAULT_TEXT_AUTO_ENCODING) 164 | SETTINGS_DWORD_VALUE(TextAnsiCodepage, UINT, L"TextAnsiCodepage", DEFAULT_TEXT_ANSI_CODEPAGE) 165 | SETTINGS_DWORD_VALUE(TextDefaultNewlines, TextNewlines, 166 | L"TextDefaultNewlines", DEFAULT_TEXT_NEWLINES) 167 | SETTINGS_BOOL_VALUE(TextAutoNewlines, L"TextAutoNewlines", DEFAULT_TEXT_AUTO_NEWLINES) 168 | 169 | bool getTrayOpenOnStartup() { 170 | return !RegGetValue(HKEY_CURRENT_USER, KEY_STARTUP, VAL_STARTUP, 171 | RRF_RT_ANY, nullptr, nullptr, nullptr); 172 | } 173 | 174 | void setTrayOpenOnStartup(bool value) { 175 | if (value) { 176 | if (getTrayOpenOnStartup()) 177 | return; // don't overwrite existing command 178 | wchar_t exePath[MAX_PATH]; 179 | if (checkLE(GetModuleFileName(nullptr, exePath, _countof(exePath)))) { 180 | local_wstr_ptr command = format(L"\"%1\" /tray", exePath); 181 | RegSetKeyValue(HKEY_CURRENT_USER, KEY_STARTUP, VAL_STARTUP, REG_EXPAND_SZ, 182 | command.get(), (lstrlen(command.get()) + 1) * sizeof(wchar_t)); 183 | } 184 | } else { 185 | RegDeleteKeyValue(HKEY_CURRENT_USER, KEY_STARTUP, VAL_STARTUP); 186 | } 187 | } 188 | 189 | SETTINGS_STRING_VALUE(TrayFolder, REG_EXPAND_SZ, L"TrayFolder", DEFAULT_TRAY_FOLDER) 190 | SETTINGS_DWORD_VALUE(TrayDPI, int, L"TrayDPI", DEFAULT_TRAY_DPI) 191 | SETTINGS_POINT_VALUE(TrayPosition, L"Tray", DEFAULT_TRAY_POSITION) 192 | SETTINGS_SIZE_VALUE(TraySize, L"Tray", DEFAULT_TRAY_SIZE) 193 | SETTINGS_DWORD_VALUE(TrayDirection, TrayDirection, L"TrayDirection", DEFAULT_TRAY_DIRECTION) 194 | SETTINGS_BOOL_VALUE(TrayHotKeyEnabled, L"TrayHotKeyEnabled", DEFAULT_TRAY_HOTKEY_ENABLED); 195 | 196 | bool supportsDefaultBrowser() { 197 | return !RegGetValue(HKEY_CLASSES_ROOT, KEY_DIRECTORY_VERB, L"", 198 | RRF_RT_ANY, nullptr, nullptr, nullptr); 199 | } 200 | 201 | void setDefaultBrowser(bool value) { 202 | const wchar_t *data = value ? DATA_BROWSER_SET : DATA_BROWSER_CLEAR; 203 | DWORD size = value ? sizeof(DATA_BROWSER_SET) : sizeof(DATA_BROWSER_CLEAR); 204 | RegSetKeyValue(HKEY_CURRENT_USER, KEY_DIRECTORY_BROWSER, L"", REG_SZ, data, size); 205 | RegSetKeyValue(HKEY_CURRENT_USER, KEY_COMPRESSED_BROWSER, L"", REG_SZ, data, size); 206 | RegSetKeyValue(HKEY_CURRENT_USER, KEY_DRIVE_BROWSER, L"", REG_SZ, data, size); 207 | } 208 | 209 | }} // namespace 210 | -------------------------------------------------------------------------------- /src/Settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | 6 | namespace chromafiler { 7 | 8 | enum TrayDirection : DWORD { TRAY_UP, TRAY_DOWN, TRAY_RIGHT }; 9 | enum TextEncoding : DWORD { ENC_UNK, ENC_UTF8, ENC_UTF8BOM, ENC_UTF16LE, ENC_UTF16BE, ENC_ANSI }; 10 | enum TextNewlines : DWORD { NL_UNK, NL_CRLF, NL_LF, NL_CR }; 11 | 12 | namespace settings { 13 | 14 | #ifdef CHROMAFILER_DEBUG 15 | extern bool testMode; 16 | #endif 17 | 18 | // http://smallvoid.com/article/winnt-shell-keyword.html 19 | const DWORD DEFAULT_LAST_OPENED_VERSION = 0; 20 | const bool DEFAULT_UPDATE_CHECK_ENABLED= false; 21 | const LONGLONG DEFAULT_LAST_UPDATE_CHECK = 0; 22 | const LONGLONG DEFAULT_UPDATE_CHECK_RATE = 10000000LL * 60 * 60 * 24 * 7; // 1 week 23 | const bool DEFAULT_ADMIN_WARNING = true; 24 | const wchar_t DEFAULT_STARTING_FOLDER[] = L"shell:Desktop"; 25 | const wchar_t DEFAULT_SCRATCH_FOLDER[] = L"shell:Desktop"; 26 | const wchar_t DEFAULT_SCRATCH_FILE_NAME[] = L"scratch.txt"; 27 | const SIZE DEFAULT_ITEM_WINDOW_SIZE = {435, 435}; 28 | const SIZE DEFAULT_FOLDER_WINDOW_SIZE = {210, 435}; 29 | const bool DEFAULT_STATUS_TEXT_ENABLED = true; 30 | const bool DEFAULT_TOOLBAR_ENABLED = true; 31 | const bool DEFAULT_PREVIEWS_ENABLED = true; 32 | const UINT DEFAULT_OPEN_SELECTION_TIME = 100; // slower than key repeat 10 and above 33 | const bool DEFAULT_DESELECT_ON_OPEN = true; 34 | const bool DEFAULT_TEXT_EDITOR_ENABLED = true; 35 | const LOGFONT DEFAULT_TEXT_FONT = { 36 | 11, 0, 0, 0, FW_REGULAR, FALSE, FALSE, FALSE, 37 | ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, 38 | DEFAULT_PITCH | FF_DONTCARE, L"Consolas" }; 39 | const int DEFAULT_TEXT_TAB_WIDTH = 4; 40 | const bool DEFAULT_TEXT_WRAP = false; 41 | const bool DEFAULT_TEXT_AUTO_INDENT = true; 42 | const TextEncoding DEFAULT_TEXT_ENCODING = ENC_UTF8; 43 | const bool DEFAULT_TEXT_AUTO_ENCODING = true; 44 | const UINT DEFAULT_TEXT_ANSI_CODEPAGE = CP_ACP; 45 | const TextNewlines DEFAULT_TEXT_NEWLINES = NL_CRLF; 46 | const bool DEFAULT_TEXT_AUTO_NEWLINES = true; 47 | const wchar_t DEFAULT_TRAY_FOLDER[] = L"shell:Links"; 48 | const int DEFAULT_TRAY_DPI = 96; 49 | const POINT DEFAULT_TRAY_POSITION = {CW_USEDEFAULT, CW_USEDEFAULT}; 50 | const SIZE DEFAULT_TRAY_SIZE = {CW_USEDEFAULT, CW_USEDEFAULT}; 51 | const TrayDirection DEFAULT_TRAY_DIRECTION = TRAY_UP; 52 | const bool DEFAULT_TRAY_HOTKEY_ENABLED = true; 53 | 54 | DWORD getLastOpenedVersion(); 55 | void setLastOpenedVersion(DWORD value); 56 | bool getUpdateCheckEnabled(); 57 | void setUpdateCheckEnabled(bool value); 58 | LONGLONG getLastUpdateCheck(); 59 | void setLastUpdateCheck(LONGLONG value); 60 | LONGLONG getUpdateCheckRate(); 61 | void setUpdateCheckRate(LONGLONG value); // TODO: add to Settings 62 | 63 | bool getAdminWarning(); 64 | void setAdminWarning(bool value); 65 | 66 | wstr_ptr getStartingFolder(); 67 | void setStartingFolder(wchar_t *value); 68 | wstr_ptr getScratchFolder(); 69 | void setScratchFolder(wchar_t *value); 70 | wstr_ptr getScratchFileName(); 71 | void setScratchFileName(wchar_t *value); 72 | 73 | SIZE getItemWindowSize(); // assuming 96 dpi 74 | void setItemWindowSize(SIZE value); 75 | SIZE getFolderWindowSize(); // assuming 96 dpi 76 | void setFolderWindowSize(SIZE value); 77 | 78 | bool getStatusTextEnabled(); 79 | void setStatusTextEnabled(bool value); 80 | bool getToolbarEnabled(); 81 | void setToolbarEnabled(bool value); 82 | 83 | bool getPreviewsEnabled(); 84 | void setPreviewsEnabled(bool value); 85 | 86 | UINT getOpenSelectionTime(); // milliseconds 87 | void setOpenSelectionTime(UINT value); // TODO: add to Settings 88 | bool getDeselectOnOpen(); 89 | void setDeselectOnOpen(bool value); // TODO: add to Settings 90 | 91 | bool getTextEditorEnabled(); 92 | void setTextEditorEnabled(bool value); 93 | LOGFONT getTextFont(); // height value is POSITIVE in POINTS (not typical) 94 | void setTextFont(const LOGFONT &value); 95 | int getTextTabWidth(); 96 | void setTextTabWidth(int value); 97 | bool getTextWrap(); 98 | void setTextWrap(bool value); 99 | bool getTextAutoIndent(); 100 | void setTextAutoIndent(bool value); 101 | TextEncoding getTextDefaultEncoding(); 102 | void setTextDefaultEncoding(TextEncoding value); 103 | bool getTextAutoEncoding(); 104 | void setTextAutoEncoding(bool value); 105 | UINT getTextAnsiCodepage(); 106 | void setTextAnsiCodepage(UINT value); // TODO: add to Settings 107 | TextNewlines getTextDefaultNewlines(); 108 | void setTextDefaultNewlines(TextNewlines value); 109 | bool getTextAutoNewlines(); 110 | void setTextAutoNewlines(bool value); 111 | 112 | bool getTrayOpenOnStartup(); 113 | void setTrayOpenOnStartup(bool value); 114 | wstr_ptr getTrayFolder(); 115 | void setTrayFolder(wchar_t *value); 116 | int getTrayDPI(); 117 | void setTrayDPI(int value); 118 | POINT getTrayPosition(); // using TrayDPI 119 | void setTrayPosition(POINT value); 120 | SIZE getTraySize(); // using TrayDPI 121 | void setTraySize(SIZE value); 122 | TrayDirection getTrayDirection(); 123 | void setTrayDirection(TrayDirection value); 124 | bool getTrayHotKeyEnabled(); 125 | void setTrayHotKeyEnabled(bool value); 126 | 127 | bool supportsDefaultBrowser(); 128 | void setDefaultBrowser(bool value); 129 | 130 | }} // namespace 131 | -------------------------------------------------------------------------------- /src/SettingsDialog.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | 6 | namespace chromafiler { 7 | 8 | enum SettingsPage { 9 | SETTINGS_GENERAL, SETTINGS_TEXT, SETTINGS_TRAY, SETTINGS_BROWSER, SETTINGS_ABOUT, 10 | NUM_SETTINGS_PAGES 11 | }; 12 | 13 | void openSettingsDialog(SettingsPage page = SETTINGS_GENERAL); 14 | bool handleSettingsDialogMessage(MSG *msg); 15 | 16 | } // namespace 17 | -------------------------------------------------------------------------------- /src/ShellUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "ShellUtils.h" 2 | 3 | namespace chromafiler { 4 | 5 | STDMETHODIMP NewItemSink::StartOperations() {return S_OK;} 6 | STDMETHODIMP NewItemSink::FinishOperations(HRESULT) {return S_OK;} 7 | STDMETHODIMP NewItemSink::PreRenameItem(DWORD, IShellItem *, LPCWSTR) {return S_OK;} 8 | STDMETHODIMP NewItemSink::PreMoveItem(DWORD, IShellItem *, IShellItem *, LPCWSTR) {return S_OK;} 9 | STDMETHODIMP NewItemSink::PreCopyItem(DWORD, IShellItem *, IShellItem *, LPCWSTR) {return S_OK;} 10 | STDMETHODIMP NewItemSink::PreDeleteItem(DWORD, IShellItem *) {return S_OK;} 11 | STDMETHODIMP NewItemSink::PostDeleteItem(DWORD, IShellItem *, HRESULT, IShellItem *) {return S_OK;} 12 | STDMETHODIMP NewItemSink::PreNewItem(DWORD, IShellItem *, LPCWSTR) {return S_OK;} 13 | STDMETHODIMP NewItemSink::UpdateProgress(UINT, UINT) {return S_OK;} 14 | STDMETHODIMP NewItemSink::ResetTimer() {return S_OK;} 15 | STDMETHODIMP NewItemSink::PauseTimer() {return S_OK;} 16 | STDMETHODIMP NewItemSink::ResumeTimer() {return S_OK;} 17 | 18 | STDMETHODIMP NewItemSink::PostRenameItem(DWORD, IShellItem *, LPCWSTR, HRESULT, 19 | IShellItem *const item) { 20 | newItem = item; 21 | return S_OK; 22 | } 23 | STDMETHODIMP NewItemSink::PostMoveItem(DWORD, IShellItem *, IShellItem *, LPCWSTR, HRESULT, 24 | IShellItem *const item) { 25 | newItem = item; 26 | return S_OK; 27 | } 28 | STDMETHODIMP NewItemSink::PostCopyItem(DWORD, IShellItem *, IShellItem *, LPCWSTR, HRESULT, 29 | IShellItem *const item) { 30 | newItem = item; 31 | return S_OK; 32 | } 33 | STDMETHODIMP NewItemSink::PostNewItem(DWORD, IShellItem *, LPCWSTR, LPCWSTR, DWORD, HRESULT, 34 | IShellItem *const item) { 35 | newItem = item; 36 | return S_OK; 37 | } 38 | 39 | STDMETHODIMP NewItemSink::QueryInterface(REFIID id, void **obj) { 40 | *obj = nullptr; 41 | if (id == __uuidof(IUnknown) || id == __uuidof(IFileOperationProgressSink)) { 42 | *obj = this; 43 | return S_OK; 44 | } 45 | return E_NOINTERFACE; 46 | }; 47 | 48 | } // namespace 49 | -------------------------------------------------------------------------------- /src/ShellUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | 4 | #include 5 | #include 6 | 7 | namespace chromafiler { 8 | 9 | // not reference counted! allocate on stack! 10 | class NewItemSink : public IFileOperationProgressSink { 11 | public: 12 | // IUnknown 13 | STDMETHODIMP QueryInterface(REFIID, void **) override; 14 | STDMETHODIMP_(ULONG) AddRef() { return 2; }; 15 | STDMETHODIMP_(ULONG) Release() { return 1; }; 16 | 17 | // IFileOperationProgressSink 18 | STDMETHODIMP StartOperations() override; 19 | STDMETHODIMP FinishOperations(HRESULT) override; 20 | STDMETHODIMP PreRenameItem(DWORD, IShellItem *, LPCWSTR) override; 21 | STDMETHODIMP PostRenameItem(DWORD, IShellItem *, LPCWSTR, HRESULT, IShellItem *) 22 | override; 23 | STDMETHODIMP PreMoveItem(DWORD, IShellItem *, IShellItem *, LPCWSTR) override; 24 | STDMETHODIMP PostMoveItem(DWORD, IShellItem *, IShellItem *, LPCWSTR, HRESULT, IShellItem *) 25 | override; 26 | STDMETHODIMP PreCopyItem(DWORD, IShellItem *, IShellItem *, LPCWSTR) override; 27 | STDMETHODIMP PostCopyItem(DWORD, IShellItem *, IShellItem *, LPCWSTR, HRESULT, IShellItem *) 28 | override; 29 | STDMETHODIMP PreDeleteItem(DWORD, IShellItem *) override; 30 | STDMETHODIMP PostDeleteItem(DWORD, IShellItem *, HRESULT, IShellItem *) override; 31 | STDMETHODIMP PreNewItem(DWORD, IShellItem *, LPCWSTR) override; 32 | STDMETHODIMP PostNewItem(DWORD, IShellItem *, LPCWSTR, LPCWSTR, DWORD, HRESULT, IShellItem *) 33 | override; 34 | STDMETHODIMP UpdateProgress(UINT, UINT) override; 35 | STDMETHODIMP ResetTimer() override; 36 | STDMETHODIMP PauseTimer() override; 37 | STDMETHODIMP ResumeTimer() override; 38 | 39 | CComPtr newItem; 40 | }; 41 | 42 | } // namespace 43 | -------------------------------------------------------------------------------- /src/TextWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "ItemWindow.h" 5 | #include "Settings.h" 6 | #include 7 | #include 8 | #include 9 | 10 | namespace chromafiler { 11 | 12 | class TextWindow : public ItemWindow { 13 | public: 14 | static void init(); 15 | 16 | TextWindow(ItemWindow *parent, IShellItem *item); 17 | 18 | static void updateAllSettings(); 19 | 20 | bool handleTopLevelMessage(MSG *msg) override; 21 | 22 | protected: 23 | enum ViewStateIndex { 24 | STATE_WORD_WRAP = ItemWindow::STATE_LAST, 25 | STATE_LAST 26 | }; 27 | enum UserMessage { 28 | // WPARAM: 0, LPARAM: 0 29 | MSG_LOAD_COMPLETE = ItemWindow::MSG_LAST, 30 | // WPARAM: HRESULT, LPARAM: 0 31 | MSG_LOAD_FAIL, 32 | MSG_LAST 33 | }; 34 | LRESULT handleMessage(UINT message, WPARAM wParam, LPARAM lParam) override; 35 | 36 | const wchar_t * appUserModelID() const override; 37 | bool useDefaultStatusText() const override; 38 | SettingsPage settingsStartPage() const override; 39 | const wchar_t * helpURL() const override; 40 | 41 | void updateWindowPropStore(IPropertyStore *propStore) override; 42 | 43 | void clearViewState(IPropertyBag *bag, uint32_t mask) override; 44 | void writeViewState(IPropertyBag *bag, uint32_t mask) override; 45 | 46 | void onCreate() override; 47 | bool onCloseRequest() override; 48 | void onDestroy() override; 49 | bool onCommand(WORD command) override; 50 | bool onControlCommand(HWND controlHwnd, WORD notif) override; 51 | LRESULT onNotify(NMHDR *nmHdr) override; 52 | void onActivate(WORD state, HWND prevWindow) override; 53 | void onSize(SIZE size) override; 54 | 55 | void addToolbarButtons(HWND tb) override; 56 | int getToolbarTooltip(WORD command) override; 57 | 58 | void trackContextMenu(POINT pos) override; 59 | 60 | private: 61 | HWND createRichEdit(bool readOnly, bool wordWrap); 62 | bool isEditable(); 63 | CComPtr getTOMDocument(); 64 | void updateFont(); 65 | void updateEditSize(); 66 | void updateStatus(); 67 | void userSave(); 68 | bool confirmSave(bool willDelete); 69 | 70 | void changeFontSize(int amount); 71 | bool isWordWrap(); 72 | void setWordWrap(bool wordWrap); 73 | void newLine(); 74 | void indentSelection(int dir); // dir can be 1 (right) or -1 (left) 75 | void lineSelect(); 76 | void openFindDialog(bool replace); 77 | void handleFindReplace(FINDREPLACE *input); 78 | void findNext(FINDREPLACE *input); 79 | void replace(FINDREPLACE *input); 80 | int replaceAll(FINDREPLACE *input); 81 | 82 | struct LoadResult { 83 | std::unique_ptr buffer; // null terminated! 84 | uint8_t *textStart; 85 | SETTEXTEX setText; 86 | TextEncoding encoding; 87 | TextNewlines newlines; 88 | }; 89 | 90 | static HRESULT loadText(IShellItem *item, LoadResult *result); 91 | HRESULT saveText(); 92 | 93 | static LRESULT CALLBACK richEditProc(HWND hwnd, UINT message, 94 | WPARAM wParam, LPARAM lParam, UINT_PTR subclassID, DWORD_PTR refData); 95 | 96 | HWND edit = nullptr; 97 | LOGFONT logFont; // NOT scaled for DPI 98 | HFONT font = nullptr; // scaled for DPI 99 | TextEncoding detectEncoding = ENC_UNK; 100 | TextNewlines detectNewlines = NL_UNK; 101 | int vScrollAccum = 0, hScrollAccum = 0; // for high resolution scrolling 102 | 103 | HWND findReplaceDialog = nullptr; 104 | FINDREPLACE findReplace; 105 | wchar_t findBuffer[128], replaceBuffer[128]; 106 | 107 | SRWLOCK asyncLoadResultLock = SRWLOCK_INIT; 108 | LoadResult asyncLoadResult; 109 | 110 | class LoadThread : public StoppableThread { 111 | public: 112 | LoadThread(IShellItem *item, TextWindow *callbackWindow); 113 | protected: 114 | void run() override; 115 | private: 116 | CComHeapPtr itemIDList; 117 | TextWindow *callbackWindow; 118 | }; 119 | CComPtr loadThread; 120 | }; 121 | 122 | } // namespace 123 | -------------------------------------------------------------------------------- /src/ThumbnailView.cpp: -------------------------------------------------------------------------------- 1 | #include "ThumbnailView.h" 2 | #include "GeomUtils.h" 3 | #include "GDIUtils.h" 4 | #include "WinUtils.h" 5 | #include 6 | 7 | namespace chromafiler { 8 | 9 | const wchar_t THUMBNAIL_CLASS[] = L"ChromaFiler Thumbnail"; 10 | 11 | static ClassFactoryImpl factory; 12 | static DWORD regCookie = 0; 13 | 14 | void ThumbnailView::init() { 15 | WNDCLASS thumbClass = {}; 16 | thumbClass.lpfnWndProc = windowProc; 17 | thumbClass.hInstance = GetModuleHandle(nullptr); 18 | thumbClass.lpszClassName = THUMBNAIL_CLASS; 19 | thumbClass.style = CS_HREDRAW | CS_VREDRAW; 20 | thumbClass.hCursor = LoadCursor(nullptr, IDC_ARROW); 21 | RegisterClass(&thumbClass); 22 | 23 | checkHR(CoRegisterClassObject(CLSID_ThumbnailView, &factory, 24 | CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, ®Cookie)); 25 | } 26 | 27 | void ThumbnailView::uninit() { 28 | checkHR(CoRevokeClassObject(regCookie)); 29 | } 30 | 31 | ThumbnailView::~ThumbnailView() { 32 | // don't need to acquire lock since thread is stopped 33 | if (thumbnailBitmap) { 34 | DeleteBitmap(thumbnailBitmap); 35 | CHROMAFILER_MEMLEAK_FREE; 36 | } 37 | } 38 | 39 | const wchar_t * ThumbnailView::className() const { 40 | return THUMBNAIL_CLASS; 41 | } 42 | 43 | LRESULT ThumbnailView::handleMessage(UINT message, WPARAM wParam, LPARAM lParam) { 44 | switch (message) { 45 | case WM_CREATE: 46 | thumbnailThread.Attach(new ThumbnailThread(item, this)); 47 | thumbnailThread->start(); 48 | return 0; 49 | case WM_DESTROY: 50 | thumbnailThread->stop(); 51 | return 0; 52 | case WM_SIZE: 53 | thumbnailThread->requestThumbnail(clientSize(hwnd)); 54 | return 0; 55 | case WM_PAINT: 56 | PAINTSTRUCT paint; 57 | BeginPaint(hwnd, &paint); 58 | onPaint(paint); 59 | EndPaint(hwnd, &paint); 60 | return 0; 61 | case WM_NCHITTEST: 62 | return HTTRANSPARENT; // allow moving window by dragging anywhere 63 | case MSG_UPDATE_THUMBNAIL_BITMAP: 64 | InvalidateRect(hwnd, nullptr, FALSE); 65 | return 0; 66 | } 67 | return DefWindowProc(hwnd, message, wParam, lParam); 68 | } 69 | 70 | void ThumbnailView::onPaint(PAINTSTRUCT paint) { 71 | AcquireSRWLockExclusive(&thumbnailBitmapLock); 72 | if (!thumbnailBitmap) { 73 | ReleaseSRWLockExclusive(&thumbnailBitmapLock); 74 | return; 75 | } 76 | SIZE size = clientSize(hwnd); 77 | 78 | HBRUSH bg = (HBRUSH)(COLOR_WINDOW + 1); 79 | BITMAP bitmap; 80 | GetObject(thumbnailBitmap, sizeof(bitmap), &bitmap); 81 | int xDest, yDest, wDest, hDest; 82 | float wScale = (float)size.cx / bitmap.bmWidth; 83 | float hScale = (float)size.cy / bitmap.bmHeight; 84 | if (wScale < hScale) { 85 | xDest = 0; 86 | wDest = size.cx; 87 | yDest = (int)((size.cy - wScale*bitmap.bmHeight) / 2); 88 | hDest = (int)(wScale*bitmap.bmHeight); 89 | FillRect(paint.hdc, tempPtr(RECT{0, 0, size.cx, yDest}), bg); 90 | FillRect(paint.hdc, tempPtr(RECT{0, yDest + hDest, size.cx, size.cy}), bg); 91 | } else { 92 | xDest = (int)((size.cx - hScale*bitmap.bmWidth) / 2); 93 | wDest = (int)(hScale*bitmap.bmWidth); 94 | yDest = 0; 95 | hDest = size.cy; 96 | FillRect(paint.hdc, tempPtr(RECT{0, 0, xDest, size.cy}), bg); 97 | FillRect(paint.hdc, tempPtr(RECT{xDest + wDest, 0, size.cx, size.cy}), bg); 98 | } 99 | 100 | HDC hdcMem = CreateCompatibleDC(paint.hdc); 101 | HBITMAP oldBitmap = SelectBitmap(hdcMem, thumbnailBitmap); 102 | SetStretchBltMode(paint.hdc, HALFTONE); 103 | StretchBlt(paint.hdc, xDest, yDest, wDest, hDest, 104 | hdcMem, 0, 0, bitmap.bmWidth, bitmap.bmHeight, SRCCOPY); 105 | SelectBitmap(hdcMem, oldBitmap); 106 | DeleteDC(hdcMem); 107 | ReleaseSRWLockExclusive(&thumbnailBitmapLock); 108 | } 109 | 110 | ThumbnailView::ThumbnailThread::ThumbnailThread( 111 | IShellItem *const item, ThumbnailView *const callbackWindow) 112 | : callbackWindow(callbackWindow) { 113 | checkHR(SHGetIDListFromObject(item, &itemIDList)); 114 | requestThumbnailEvent = checkLE(CreateEvent(nullptr, TRUE, FALSE, nullptr)); 115 | } 116 | 117 | ThumbnailView::ThumbnailThread::~ThumbnailThread() { 118 | checkLE(CloseHandle(requestThumbnailEvent)); 119 | } 120 | 121 | void ThumbnailView::ThumbnailThread::requestThumbnail(SIZE size) { 122 | AcquireSRWLockExclusive(&requestThumbnailLock); 123 | requestedSize = size; 124 | checkLE(SetEvent(requestThumbnailEvent)); 125 | ReleaseSRWLockExclusive(&requestThumbnailLock); 126 | } 127 | 128 | void ThumbnailView::ThumbnailThread::run() { 129 | CComPtr imageFactory; 130 | if (!itemIDList || !checkHR(SHCreateItemFromIDList(itemIDList, IID_PPV_ARGS(&imageFactory)))) 131 | return; 132 | itemIDList.Free(); 133 | 134 | HANDLE waitObjects[] = {requestThumbnailEvent, stopEvent}; 135 | int event; 136 | while ((event = WaitForMultipleObjects( 137 | _countof(waitObjects), waitObjects, FALSE, INFINITE)) != WAIT_FAILED) { 138 | if (event == WAIT_OBJECT_0) { 139 | SIZE size; 140 | AcquireSRWLockExclusive(&requestThumbnailLock); 141 | size = requestedSize; 142 | checkLE(ResetEvent(requestThumbnailEvent)); 143 | ReleaseSRWLockExclusive(&requestThumbnailLock); 144 | 145 | HBITMAP hBitmap; 146 | if (FAILED(imageFactory->GetImage(size, 147 | SIIGBF_BIGGERSIZEOK | SIIGBF_THUMBNAILONLY, &hBitmap))) { 148 | // no thumbnail, fallback to icon 149 | int minDim = max(1, min(size.cx, size.cy)); // make square (for Windows 7) 150 | if (!checkHR(imageFactory->GetImage({minDim, minDim}, 151 | SIIGBF_BIGGERSIZEOK | SIIGBF_ICONONLY, &hBitmap))) 152 | return; 153 | } 154 | BITMAP bitmap; 155 | GetObject(hBitmap, sizeof(bitmap), &bitmap); 156 | compositeBackground(bitmap); // TODO change color for high contrast themes 157 | 158 | // ensure the window is not closed before the message is posted 159 | AcquireSRWLockExclusive(&stopLock); 160 | if (isStopped()) { 161 | DeleteBitmap(hBitmap); 162 | } else { 163 | AcquireSRWLockExclusive(&callbackWindow->thumbnailBitmapLock); 164 | if (callbackWindow->thumbnailBitmap) { 165 | DeleteBitmap(callbackWindow->thumbnailBitmap); 166 | CHROMAFILER_MEMLEAK_FREE; 167 | } 168 | callbackWindow->thumbnailBitmap = hBitmap; 169 | PostMessage(callbackWindow->hwnd, MSG_UPDATE_THUMBNAIL_BITMAP, 0, 0); 170 | CHROMAFILER_MEMLEAK_ALLOC; 171 | ReleaseSRWLockExclusive(&callbackWindow->thumbnailBitmapLock); 172 | } 173 | ReleaseSRWLockExclusive(&stopLock); 174 | } else if (event == WAIT_OBJECT_0 + 1) { 175 | return; // stop 176 | } 177 | } 178 | return; 179 | } 180 | 181 | } // namespace 182 | -------------------------------------------------------------------------------- /src/ThumbnailView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "PreviewHandler.h" 5 | 6 | namespace chromafiler { 7 | 8 | // {80f502d3-92c4-4773-9333-1edd9e48d7d3} 9 | const CLSID CLSID_ThumbnailView = 10 | {0x80f502d3, 0x92c4, 0x4773, {0x93, 0x33, 0x1e, 0xdd, 0x9e, 0x48, 0xd7, 0xd3}}; 11 | class ThumbnailView : public PreviewHandlerImpl { 12 | public: 13 | static void init(); 14 | static void uninit(); 15 | 16 | ~ThumbnailView(); 17 | 18 | protected: 19 | enum UserMessage { 20 | // WPARAM: 0, LPARAM: 0 21 | MSG_UPDATE_THUMBNAIL_BITMAP = WM_USER, 22 | MSG_LAST 23 | }; 24 | const wchar_t * className() const override; 25 | LRESULT handleMessage(UINT message, WPARAM wParam, LPARAM lParam) override; 26 | 27 | private: 28 | void onPaint(PAINTSTRUCT paint); 29 | 30 | SRWLOCK thumbnailBitmapLock = SRWLOCK_INIT; 31 | HBITMAP thumbnailBitmap = nullptr; 32 | 33 | class ThumbnailThread : public StoppableThread { 34 | public: 35 | ThumbnailThread(IShellItem *item, ThumbnailView *callbackWindow); 36 | ~ThumbnailThread(); 37 | void requestThumbnail(SIZE size); 38 | protected: 39 | void run() override; 40 | private: 41 | CComHeapPtr itemIDList; 42 | ThumbnailView *callbackWindow; 43 | HANDLE requestThumbnailEvent; 44 | SRWLOCK requestThumbnailLock = SRWLOCK_INIT; 45 | SIZE requestedSize; 46 | }; 47 | 48 | CComPtr thumbnailThread; 49 | }; 50 | 51 | } // namespace 52 | -------------------------------------------------------------------------------- /src/TrayWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "FolderWindow.h" 5 | 6 | namespace chromafiler { 7 | 8 | // about the name: https://devblogs.microsoft.com/oldnewthing/20030910-00/?p=42583 9 | class TrayWindow : public FolderWindow { 10 | public: 11 | static void init(); 12 | 13 | static HWND findTray(); 14 | static void resetTrayPosition(); 15 | static void updateAllSettings(); 16 | 17 | TrayWindow(ItemWindow *parent, IShellItem *item); 18 | 19 | SIZE requestedSize() override; 20 | RECT requestedRect(HMONITOR preferMonitor) override; 21 | 22 | bool handleTopLevelMessage(MSG *msg) override; 23 | 24 | STDMETHODIMP OnNavigationComplete(PCIDLIST_ABSOLUTE folder) override; 25 | STDMETHODIMP MessageSFVCB(UINT msg, WPARAM wParam, LPARAM lParam) override; 26 | 27 | protected: 28 | enum UserMessage { 29 | MSG_APPBAR_CALLBACK = FolderWindow::MSG_LAST, 30 | MSG_LAST 31 | }; 32 | enum TimerID { 33 | TIMER_MAKE_TOPMOST = FolderWindow::TIMER_LAST, 34 | TIMER_LAST 35 | }; 36 | LRESULT handleMessage(UINT message, WPARAM wParam, LPARAM lParam) override; 37 | 38 | DWORD windowStyle() const override; 39 | DWORD windowExStyle() const override; 40 | bool useCustomFrame() const override; 41 | bool paletteWindow() const override; 42 | bool stickToChild() const override; 43 | SettingsPage settingsStartPage() const override; 44 | 45 | POINT childPos(SIZE size) override; 46 | const wchar_t * propBagName() const override; 47 | void initDefaultView(IFolderView2 *folderView) override; 48 | FOLDERSETTINGS folderSettings() const override; 49 | 50 | RECT windowBody() override; 51 | 52 | void onCreate() override; 53 | void onDestroy() override; 54 | bool onCommand(WORD command) override; 55 | void onSize(SIZE size) override; 56 | 57 | private: 58 | const wchar_t * className() const override; 59 | 60 | void updateSettings(); 61 | void fixListViewColors(); 62 | 63 | void forceTopmost(); 64 | 65 | static LRESULT CALLBACK sizeGripProc(HWND hwnd, UINT message, 66 | WPARAM wParam, LPARAM lParam, UINT_PTR subclassID, DWORD_PTR refData); 67 | 68 | HWND traySizeGrip; 69 | bool fullScreen = false; 70 | POINT movePos; 71 | }; 72 | 73 | } // namespace 74 | -------------------------------------------------------------------------------- /src/UIStrings.cpp: -------------------------------------------------------------------------------- 1 | #include "UIStrings.h" 2 | #include "DPI.h" 3 | 4 | namespace chromafiler { 5 | 6 | const wchar_t * getString(UINT id) { 7 | // must use /n flag with rc.exe! 8 | wchar_t *str = NULL; 9 | LoadString(GetModuleHandle(nullptr), id, (wchar_t *)&str, 0); 10 | return str; 11 | } 12 | 13 | local_wstr_ptr formatArgs(const wchar_t *format, va_list *args) { 14 | wchar_t *buffer = nullptr; 15 | FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, 16 | format, 0, 0, (wchar_t *)&buffer, 0, args); 17 | return local_wstr_ptr(buffer); 18 | } 19 | 20 | local_wstr_ptr format(const wchar_t *format, ...) { 21 | va_list args = nullptr; 22 | va_start(args, format); 23 | local_wstr_ptr str = formatArgs(format, &args); 24 | va_end(args); 25 | return str; 26 | } 27 | 28 | local_wstr_ptr formatString(UINT id, ...) { 29 | va_list args = nullptr; 30 | va_start(args, id); 31 | local_wstr_ptr str = formatArgs(getString(id), &args); 32 | va_end(args); 33 | return str; 34 | } 35 | 36 | local_wstr_ptr getErrorMessage(DWORD error) { 37 | // based on _com_error::ErrorMessage() (comdef.h) 38 | HMODULE mod = nullptr; 39 | if (error >= 12000 && error <= 12190) 40 | mod = GetModuleHandle(L"wininet.dll"); 41 | wchar_t *buffer = nullptr; 42 | DWORD result = FormatMessage((mod ? FORMAT_MESSAGE_FROM_HMODULE : FORMAT_MESSAGE_FROM_SYSTEM) 43 | | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, 44 | mod, error, 0, (wchar_t *)&buffer, 0, nullptr); 45 | if (result && buffer) { 46 | int len = lstrlen(buffer); 47 | if (len > 1 && buffer[len - 1] == '\n') { 48 | buffer[len - 1] = 0; 49 | if (buffer[len - 2] == '\r') 50 | buffer[len - 2] = 0; 51 | } 52 | return local_wstr_ptr(buffer); 53 | } else { 54 | if (buffer) 55 | LocalFree(buffer); 56 | return formatString(IDS_UNKNOWN_ERROR); 57 | } 58 | } 59 | 60 | 61 | void showDebugMessage(HWND owner, const wchar_t *title, const wchar_t *format, ...) { 62 | va_list args = nullptr; 63 | va_start(args, format); 64 | local_wstr_ptr str = formatArgs(format, &args); 65 | va_end(args); 66 | 67 | HWND edit = checkLE(CreateWindow(L"EDIT", title, 68 | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_VSCROLL | WS_HSCROLL 69 | | ES_AUTOHSCROLL | ES_LEFT | ES_MULTILINE | ES_READONLY, 70 | CW_USEDEFAULT, CW_USEDEFAULT, scaleDPI(400), scaleDPI(200), 71 | owner, nullptr, GetModuleHandle(nullptr), nullptr)); 72 | SendMessage(edit, WM_SETTEXT, 0, (LPARAM)str.get()); 73 | ShowWindow(edit, SW_SHOWNORMAL); 74 | } 75 | 76 | } // namespace 77 | -------------------------------------------------------------------------------- /src/UIStrings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "resource.h" 5 | #include 6 | #include 7 | 8 | #define XSTRINGIFY(X) STRINGIFY(X) 9 | #define STRINGIFY(X) #X 10 | 11 | namespace chromafiler { 12 | 13 | struct LocalDeleter { 14 | template 15 | void operator()(T* p) { 16 | LocalFree(p); 17 | } 18 | }; 19 | 20 | using local_wstr_ptr = std::unique_ptr; 21 | 22 | const wchar_t * getString(UINT id); 23 | local_wstr_ptr format(const wchar_t *format, ...); 24 | local_wstr_ptr formatString(UINT id, ...); 25 | 26 | local_wstr_ptr getErrorMessage(DWORD error); 27 | 28 | void showDebugMessage(HWND owner, const wchar_t *title, const wchar_t *format, ...); 29 | 30 | } // namespace 31 | -------------------------------------------------------------------------------- /src/Update.cpp: -------------------------------------------------------------------------------- 1 | #include "Update.h" 2 | #include "Settings.h" 3 | #include "resource.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace chromafiler { 11 | 12 | const wchar_t UPDATE_URL[] = L"https://chroma.zone/dist/chromafiler-update-release.txt"; 13 | const int MAX_DOWNLOAD_SIZE = 1024; 14 | 15 | static UpdateInfo updateInfo; 16 | static HANDLE updateThread = nullptr; 17 | 18 | DWORD WINAPI updateCheckProc(void *); 19 | 20 | void autoUpdateCheck() { 21 | if (settings::getUpdateCheckEnabled()) { 22 | LONGLONG lastCheck = settings::getLastUpdateCheck(); 23 | LONGLONG interval = settings::getUpdateCheckRate(); 24 | FILETIME fileTime; 25 | GetSystemTimeAsFileTime(&fileTime); // https://stackoverflow.com/a/1695332/11525734 26 | LONGLONG curTime = (LONGLONG)fileTime.dwLowDateTime 27 | + ((LONGLONG)(fileTime.dwHighDateTime) << 32LL); 28 | if (curTime - lastCheck > interval) { 29 | if (updateThread) { 30 | if (WaitForSingleObject(updateThread, 0) == WAIT_OBJECT_0) { 31 | checkLE(CloseHandle(updateThread)); 32 | updateThread = nullptr; 33 | } else { 34 | return; // still running! 35 | } 36 | } 37 | settings::setLastUpdateCheck(curTime); 38 | SHCreateThreadWithHandle(updateCheckProc, nullptr, 0, nullptr, &updateThread); 39 | } 40 | } 41 | } 42 | 43 | void waitForUpdateThread() { 44 | if (updateThread) { 45 | WaitForSingleObject(updateThread, INFINITE); 46 | checkLE(CloseHandle(updateThread)); 47 | } 48 | } 49 | 50 | LRESULT CALLBACK notifyWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { 51 | if (message == WM_USER) { 52 | if (LOWORD(lParam) == NIN_BALLOONUSERCLICK) { 53 | openUpdate(updateInfo); 54 | PostQuitMessage(0); 55 | } else if (LOWORD(lParam) == NIN_BALLOONTIMEOUT || LOWORD(lParam) == NIN_BALLOONHIDE) { 56 | PostQuitMessage(0); 57 | } 58 | return 0; 59 | } 60 | return DefWindowProc(hwnd, message, wParam, lParam); 61 | } 62 | 63 | DWORD WINAPI updateCheckProc(void *) { 64 | debugPrintf(L"Checking for updates...\n"); 65 | if (checkUpdate(&updateInfo) || !updateInfo.isNewer) 66 | return 0; 67 | debugPrintf(L"Update available!\n"); 68 | 69 | HINSTANCE instance = GetModuleHandle(nullptr); 70 | WNDCLASS notifyClass = {}; 71 | notifyClass.lpfnWndProc = notifyWindowProc; 72 | notifyClass.hInstance = instance; 73 | notifyClass.lpszClassName = L"ChromaFile Update Notify"; 74 | if (!checkLE(RegisterClass(¬ifyClass))) 75 | return 0; 76 | HWND messageWindow = checkLE(CreateWindow(notifyClass.lpszClassName, L"ChromaFiler", 0, 77 | 0, 0, 0, 0, HWND_MESSAGE, nullptr, instance, nullptr)); 78 | if (!messageWindow) 79 | return 0; 80 | 81 | // https://learn.microsoft.com/en-us/windows/win32/shell/notification-area 82 | NOTIFYICONDATA notify = {sizeof(notify)}; 83 | notify.uFlags = NIF_INFO | NIF_ICON | NIF_MESSAGE; 84 | notify.uCallbackMessage = WM_USER; 85 | notify.hWnd = messageWindow; 86 | notify.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 87 | notify.uVersion = NOTIFYICON_VERSION_4; 88 | LoadString(instance, IDS_UPDATE_NOTIF_INFO, notify.szInfo, _countof(notify.szInfo)); 89 | LoadString(instance, IDS_UPDATE_NOTIF_TITLE, notify.szInfoTitle, _countof(notify.szInfoTitle)); 90 | checkHR(LoadIconMetric(instance, MAKEINTRESOURCE(IDR_APP_ICON), LIM_SMALL, ¬ify.hIcon)); 91 | if (!checkLE(Shell_NotifyIcon(NIM_ADD, ¬ify))) 92 | return 0; 93 | checkLE(Shell_NotifyIcon(NIM_SETVERSION, ¬ify)); 94 | 95 | // keep thread running to keep message window open 96 | MSG msg; 97 | while (GetMessage(&msg, nullptr, 0, 0)) { 98 | TranslateMessage(&msg); 99 | DispatchMessage(&msg); 100 | } 101 | checkLE(Shell_NotifyIcon(NIM_DELETE, ¬ify)); 102 | return 0; 103 | } 104 | 105 | DWORD checkUpdate(UpdateInfo *info) { 106 | std::vector data; 107 | DWORD dataSize = 0; 108 | DWORD error = (DWORD)E_FAIL; 109 | 110 | // https://learn.microsoft.com/en-us/windows/win32/wininet/http-sessions 111 | HINTERNET internet = InternetOpen(L"ChromaFiler/1.0", INTERNET_OPEN_TYPE_PRECONFIG, 112 | nullptr, nullptr, 0); 113 | if (!checkLE(internet)) 114 | return GetLastError(); 115 | HINTERNET connection = InternetOpenUrl(internet, UPDATE_URL, nullptr, 0, 116 | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_RELOAD, 0); 117 | if (!checkLE(connection)) { 118 | error = GetLastError(); 119 | } else { 120 | while (dataSize < MAX_DOWNLOAD_SIZE) { 121 | DWORD bufferSize, downloadedSize; 122 | if (!checkLE(InternetQueryDataAvailable(connection, &bufferSize, 0, 0))) { 123 | error = GetLastError(); 124 | break; 125 | } 126 | if (dataSize + bufferSize > MAX_DOWNLOAD_SIZE) 127 | bufferSize = MAX_DOWNLOAD_SIZE - dataSize; 128 | data.resize(dataSize + bufferSize); 129 | if (!checkLE(InternetReadFile(connection, data.data() + dataSize, bufferSize, 130 | &downloadedSize))) { 131 | error = GetLastError(); 132 | break; 133 | } 134 | if (downloadedSize == 0) 135 | break; 136 | 137 | dataSize += downloadedSize; 138 | error = 0; 139 | } 140 | data.resize(dataSize + 1); 141 | data[dataSize] = 0; // null terminator 142 | InternetCloseHandle(connection); 143 | } 144 | InternetCloseHandle(internet); 145 | 146 | if (error) 147 | return error; 148 | debugPrintf(L"Downloaded content: %S\n", data.data()); 149 | if (dataSize < 31) { 150 | debugPrintf(L"Update data is too small!\n"); 151 | return (DWORD)E_FAIL; 152 | } 153 | if (memcmp(data.data(), "CFUP", 4)) { 154 | debugPrintf(L"Update data is missing prefix!\n"); 155 | return (DWORD)E_FAIL; 156 | } 157 | char hexString[11] = "0x"; 158 | CopyMemory(hexString + 2, data.data() + 5, 8); 159 | hexString[10] = 0; 160 | if (!StrToIntExA(hexString, STIF_SUPPORT_HEX, (int *)&info->version)) { 161 | debugPrintf(L"Can't parse update version!\n"); 162 | return (DWORD)E_FAIL; 163 | } 164 | info->isNewer = info->version > makeVersion(CHROMAFILER_VERSION); 165 | 166 | char *url = data.data() + 14, *urlEnd = StrChrA(url, '\n'); 167 | size_t urlLen = urlEnd ? (urlEnd - url) : lstrlenA(url); 168 | info->url = std::unique_ptr(new char[urlLen + 1]); 169 | CopyMemory(info->url.get(), url, urlLen); 170 | info->url[urlLen] = 0; 171 | return S_OK; 172 | } 173 | 174 | void openUpdate(const UpdateInfo &info) { 175 | ShellExecuteA(nullptr, "open", info.url.get(), nullptr, nullptr, SW_SHOWNORMAL); 176 | } 177 | 178 | } // namespace 179 | -------------------------------------------------------------------------------- /src/Update.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | namespace chromafiler { 8 | 9 | struct UpdateInfo { 10 | DWORD version; 11 | bool isNewer; 12 | std::unique_ptr url; 13 | }; 14 | 15 | constexpr DWORD makeVersion(BYTE v1, BYTE v2, BYTE v3, BYTE v4) { 16 | return (v1 << 24) | (v2 << 16) | (v3 << 8) | v4; 17 | } 18 | 19 | void autoUpdateCheck(); 20 | void waitForUpdateThread(); 21 | DWORD checkUpdate(UpdateInfo *info); 22 | void openUpdate(const UpdateInfo &info); 23 | 24 | } // namespace 25 | -------------------------------------------------------------------------------- /src/WinUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "WinUtils.h" 2 | #include 3 | 4 | namespace chromafiler { 5 | 6 | RECT windowRect(HWND hwnd) { 7 | RECT rect = {}; 8 | checkLE(GetWindowRect(hwnd, &rect)); 9 | return rect; 10 | } 11 | 12 | RECT clientRect(HWND hwnd) { 13 | RECT rect = {}; 14 | checkLE(GetClientRect(hwnd, &rect)); 15 | return rect; 16 | } 17 | 18 | SIZE clientSize(HWND hwnd) { 19 | RECT rect = clientRect(hwnd); 20 | return {rect.right, rect.bottom}; 21 | } 22 | 23 | POINT screenToClient(HWND hwnd, POINT screenPt) { 24 | checkLE(ScreenToClient(hwnd, &screenPt)); 25 | return screenPt; 26 | } 27 | 28 | POINT clientToScreen(HWND hwnd, POINT clientPt) { 29 | checkLE(ClientToScreen(hwnd, &clientPt)); 30 | return clientPt; 31 | } 32 | 33 | LRESULT CALLBACK WindowImpl::windowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { 34 | WindowImpl *self; 35 | if (message == WM_NCCREATE) { 36 | self = (WindowImpl *)((CREATESTRUCT *)lParam)->lpCreateParams; 37 | self->hwnd = hwnd; 38 | SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)self); 39 | } else { 40 | self = (WindowImpl *)GetWindowLongPtr(hwnd, GWLP_USERDATA); 41 | } 42 | if (self) { 43 | if (self->useCustomFrame()) { 44 | // https://docs.microsoft.com/en-us/windows/win32/dwm/customframe 45 | LRESULT dwmResult = 0; 46 | if (DwmDefWindowProc(hwnd, message, wParam, lParam, &dwmResult)) 47 | return dwmResult; 48 | } 49 | return self->handleMessage(message, wParam, lParam); 50 | } else { 51 | return DefWindowProc(hwnd, message, wParam, lParam); 52 | } 53 | } 54 | 55 | LRESULT WindowImpl::handleMessage(UINT message, WPARAM wParam, LPARAM lParam) { 56 | return DefWindowProc(hwnd, message, wParam, lParam); 57 | } 58 | 59 | bool WindowImpl::useCustomFrame() const { 60 | return false; 61 | } 62 | 63 | } // namespace 64 | -------------------------------------------------------------------------------- /src/WinUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | namespace chromafiler { 8 | 9 | // https://stackoverflow.com/a/47459319 10 | template 11 | const T* tempPtr(const T&& x) { return &x; } 12 | 13 | using wstr_ptr = std::unique_ptr; 14 | 15 | /* win32 wrappers that return values directly instead of error codes */ 16 | RECT windowRect(HWND hwnd); 17 | RECT clientRect(HWND hwnd); // client rect top-left is always at (0,0) 18 | SIZE clientSize(HWND hwnd); 19 | POINT screenToClient(HWND hwnd, POINT screenPt); 20 | POINT clientToScreen(HWND hwnd, POINT clientPt); 21 | 22 | class WindowImpl { 23 | protected: 24 | static LRESULT CALLBACK windowProc(HWND, UINT, WPARAM, LPARAM); 25 | virtual LRESULT handleMessage(UINT message, WPARAM wParam, LPARAM lParam); 26 | virtual bool useCustomFrame() const; 27 | HWND hwnd = nullptr; 28 | }; 29 | 30 | } // namespace 31 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // win32 4 | #ifndef UNICODE 5 | #define UNICODE 6 | #endif 7 | #define STRICT 8 | #define WIN32_LEAN_AND_MEAN 9 | // target Windows 7 10 | // https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt 11 | #define WINVER 0x0601 12 | #define _WIN32_WINNT 0x0601 13 | 14 | 15 | #ifdef CHROMAFILER_DEBUG 16 | #define debugPrintf wprintf 17 | // use checkHR instead of SUCCEEDED when you expect the call to succeed 18 | #define checkHR(hr) logHRESULT((hr), __FILE__, __LINE__, #hr) 19 | bool logHRESULT(long hr, const char *file, int line, const char *expr); 20 | #define checkLE(expr) checkLETemplate((expr), __FILE__, __LINE__, #expr) 21 | void logLastError(const char *file, int line, const char *expr); 22 | template 23 | T checkLETemplate(T result, const char *file, int line, const char *expr) { 24 | if (!result) 25 | logLastError(file, line, expr); 26 | return result; 27 | } 28 | #else 29 | #define debugPrintf(...) 30 | #define checkHR SUCCEEDED 31 | #define checkLE(expr) (expr) 32 | #endif 33 | 34 | #ifdef CHROMAFILER_MEMLEAKS 35 | // https://docs.microsoft.com/en-us/visualstudio/debugger/finding-memory-leaks-using-the-crt-library 36 | #define _CRTDBG_MAP_ALLOC 37 | #include 38 | #include 39 | namespace chromafiler { 40 | extern long MEMLEAK_COUNT; 41 | } 42 | #define CHROMAFILER_MEMLEAK_ALLOC InterlockedIncrement(&MEMLEAK_COUNT) 43 | #define CHROMAFILER_MEMLEAK_FREE InterlockedDecrement(&MEMLEAK_COUNT) 44 | #else 45 | #define CHROMAFILER_MEMLEAK_ALLOC 46 | #define CHROMAFILER_MEMLEAK_FREE 47 | #endif 48 | -------------------------------------------------------------------------------- /src/dialog.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define IDR_APP_ICON 101 4 | 5 | #define IDD_SETTINGS_GENERAL 104 6 | #define IDC_START_FOLDER_PATH 1001 7 | #define IDC_START_FOLDER_BROWSE 1002 8 | #define IDC_FOLDER_WINDOW_WIDTH 1003 9 | #define IDC_FOLDER_WINDOW_HEIGHT 1004 10 | #define IDC_ITEM_WINDOW_WIDTH 1005 11 | #define IDC_ITEM_WINDOW_HEIGHT 1006 12 | #define IDC_STATUS_TEXT_ENABLED 1007 13 | #define IDC_TOOLBAR_ENABLED 1008 14 | #define IDC_PREVIEWS_ENABLED 1009 15 | #define IDC_EXPLORER_SETTINGS 1010 16 | #define IDC_SELECTION_DELAY 1011 17 | #define IDC_SELECTION_DELAY_UD 1012 18 | 19 | #define IDD_SETTINGS_TRAY 105 20 | #define IDC_TRAY_ENABLED 1101 21 | #define IDC_OPEN_TRAY_ON_STARTUP 1102 22 | #define IDC_TRAY_FOLDER_PATH 1103 23 | #define IDC_TRAY_FOLDER_BROWSE 1104 24 | #define IDC_TRAY_DIR_ABOVE 1105 25 | #define IDC_TRAY_DIR_BELOW 1106 26 | #define IDC_TRAY_DIR_RIGHT 1107 27 | #define IDC_RESET_TRAY_POSITION 1108 28 | #define IDC_TRAY_HOTKEY 1109 29 | 30 | #define IDD_SETTINGS_TEXT 109 31 | #define IDC_TEXT_EDITOR_ENABLED 1201 32 | #define IDC_TEXT_FONT 1202 33 | #define IDC_TEXT_FONT_NAME 1203 34 | #define IDC_TEXT_AUTO_INDENT 1205 35 | #define IDC_SCRATCH_FOLDER_PATH 1206 36 | #define IDC_SCRATCH_FOLDER_BROWSE 1207 37 | #define IDC_SCRATCH_FILE_NAME 1208 38 | #define IDC_TEXT_NEWLINES 1209 39 | #define IDC_TEXT_AUTO_NEWLINES 1210 40 | #define IDC_TEXT_ENCODING 1211 41 | #define IDC_TEXT_AUTO_ENCODING 1212 42 | #define IDC_TEXT_TAB_SIZE 1213 43 | #define IDC_TEXT_TAB_SIZE_UD 1214 44 | 45 | #define IDD_SETTINGS_BROWSER 110 46 | #define IDC_SET_DEFAULT_BROWSER 1301 47 | #define IDC_RESET_DEFAULT_BROWSER 1302 48 | 49 | #define IDD_SETTINGS_ABOUT 106 50 | #define IDC_UPDATES_LINK 1401 51 | #define IDC_HELP_LINK 1402 52 | #define IDC_WEBSITE_LINK 1403 53 | #define IDC_AUTO_UPDATE 1404 54 | #define IDC_LEGAL_INFO 1405 55 | #define IDC_VERSION 1406 56 | #define IDC_DONATE_LINK 1407 57 | -------------------------------------------------------------------------------- /src/dialog.rc: -------------------------------------------------------------------------------- 1 | // RESOURCE SCRIPT generated by "Pelles C for Windows, version 12.00". 2 | 3 | #include 4 | #include 5 | #include 6 | #include "dialog.h" 7 | 8 | LANGUAGE LANG_ENGLISH,SUBLANG_ENGLISH_US 9 | 10 | IDD_SETTINGS_GENERAL DIALOGEX DISCARDABLE 0, 0, 231, 217 11 | STYLE DS_SHELLFONT|WS_CHILD|WS_CAPTION|WS_DISABLED 12 | CAPTION "General" 13 | FONT 8, "MS Shell Dlg", 400, 0, 0 14 | { 15 | CONTROL "&Starting folder", -1, "Button", BS_GROUPBOX, 7, 7, 217, 35 16 | CONTROL "", IDC_START_FOLDER_PATH, "ComboBoxEx32", CBS_DROPDOWN|WS_TABSTOP|0x00000040, 14, 21, 140, 300 17 | CONTROL "&Browse...", IDC_START_FOLDER_BROWSE, "Button", WS_TABSTOP, 161, 21, 56, 14 18 | CONTROL "Default window sizes", -1, "Button", BS_GROUPBOX, 7, 49, 119, 49 19 | CONTROL "&Folder", -1, "Static", SS_RIGHT|WS_GROUP, 14, 66, 42, 8 20 | CONTROL "", IDC_FOLDER_WINDOW_WIDTH, "Edit", ES_NUMBER|WS_BORDER|WS_TABSTOP, 63, 63, 21, 14 21 | CONTROL "x", -1, "Static", SS_CENTER|WS_GROUP, 85, 66, 12, 8 22 | CONTROL "", IDC_FOLDER_WINDOW_HEIGHT, "Edit", ES_NUMBER|WS_BORDER|WS_TABSTOP, 98, 63, 21, 14 23 | CONTROL "File &preview", -1, "Static", SS_RIGHT|WS_GROUP, 14, 80, 42, 8 24 | CONTROL "", IDC_ITEM_WINDOW_WIDTH, "Edit", ES_NUMBER|WS_BORDER|WS_TABSTOP, 63, 77, 21, 14 25 | CONTROL "x", -1, "Static", SS_CENTER|WS_GROUP, 85, 80, 12, 8 26 | CONTROL "", IDC_ITEM_WINDOW_HEIGHT, "Edit", ES_NUMBER|WS_BORDER|WS_TABSTOP, 98, 77, 21, 14 27 | CONTROL "Open selection &delay:", -1, "Static", WS_GROUP, 133, 52, 91, 8 28 | CONTROL "Edit", IDC_SELECTION_DELAY, "Edit", ES_NUMBER|WS_BORDER|WS_TABSTOP, 133, 63, 21, 14 29 | CONTROL "", IDC_SELECTION_DELAY_UD, "msctls_updown32", UDS_AUTOBUDDY|UDS_SETBUDDYINT|UDS_ARROWKEYS, 154, 63, 14, 14 30 | CONTROL "milliseconds", -1, "Static", WS_GROUP, 168, 66, 40, 8 31 | CONTROL "Toolbar", -1, "Button", BS_GROUPBOX, 7, 105, 105, 49 32 | CONTROL "Show i&nfo text", IDC_STATUS_TEXT_ENABLED, "Button", BS_AUTOCHECKBOX|WS_TABSTOP, 14, 119, 91, 14 33 | CONTROL "Show &toolbar", IDC_TOOLBAR_ENABLED, "Button", BS_AUTOCHECKBOX|WS_TABSTOP, 14, 133, 91, 14 34 | CONTROL "Previews", -1, "Button", BS_GROUPBOX, 119, 105, 105, 49 35 | CONTROL "Use Preview &Handlers", IDC_PREVIEWS_ENABLED, "Button", BS_AUTOCHECKBOX|WS_TABSTOP, 126, 119, 91, 14 36 | CONTROL "File Explorer options", -1, "Button", BS_GROUPBOX, 7, 161, 217, 35 37 | CONTROL "Some File Explorer options also affect ChromaFiler folder windows.", -1, "Static", WS_GROUP, 14, 175, 140, 16 38 | CONTROL "&Options...", IDC_EXPLORER_SETTINGS, "Button", WS_TABSTOP, 161, 175, 56, 14 39 | } 40 | 41 | IDD_SETTINGS_TRAY DIALOGEX DISCARDABLE 0, 0, 231, 217 42 | STYLE DS_SHELLFONT|WS_CHILD|WS_CAPTION|WS_DISABLED 43 | CAPTION "Tray" 44 | FONT 8, "MS Shell Dlg", 400, 0, 0 45 | { 46 | CONTROL "Show &tray", IDC_TRAY_ENABLED, "Button", BS_AUTOCHECKBOX|WS_TABSTOP, 7, 7, 105, 14 47 | CONTROL "Open tray on &startup", IDC_OPEN_TRAY_ON_STARTUP, "Button", BS_AUTOCHECKBOX|WS_TABSTOP, 119, 7, 105, 14 48 | CONTROL "Enable Win+Alt+C &hotkey to focus tray", IDC_TRAY_HOTKEY, "Button", BS_AUTOCHECKBOX|WS_TABSTOP, 7, 21, 217, 14 49 | CONTROL "Tray &folder", -1, "Button", BS_GROUPBOX, 7, 42, 217, 35 50 | CONTROL "", IDC_TRAY_FOLDER_PATH, "ComboBoxEx32", CBS_DROPDOWN|WS_TABSTOP|0x00000040, 14, 56, 140, 300 51 | CONTROL "&Browse...", IDC_TRAY_FOLDER_BROWSE, "Button", WS_TABSTOP, 161, 56, 56, 14 52 | CONTROL "Tray direction", -1, "Button", BS_GROUPBOX, 7, 84, 217, 35 53 | CONTROL "Open items...", -1, "Static", WS_GROUP, 14, 101, 56, 8 54 | CONTROL "Abo&ve", IDC_TRAY_DIR_ABOVE, "Button", BS_AUTORADIOBUTTON, 70, 98, 42, 14 55 | CONTROL "Belo&w", IDC_TRAY_DIR_BELOW, "Button", BS_AUTORADIOBUTTON, 112, 98, 42, 14 56 | CONTROL "&Right", IDC_TRAY_DIR_RIGHT, "Button", BS_AUTORADIOBUTTON, 154, 98, 42, 14 57 | CONTROL 32516, -1, "Static", SS_ICON, 14, 128, 0, 0 58 | CONTROL "Drag the upper-left corner of the tray to move. Drag the lower-right corner to resize.", -1, "Static", WS_GROUP, 42, 129, 175, 16 59 | CONTROL "R&eset tray position", IDC_RESET_TRAY_POSITION, "Button", WS_TABSTOP, 42, 154, 70, 14 60 | } 61 | 62 | IDD_SETTINGS_TEXT DIALOGEX DISCARDABLE 0, 0, 231, 217 63 | STYLE DS_SHELLFONT|WS_CHILD|WS_CAPTION|WS_DISABLED 64 | CAPTION "Text Editor" 65 | FONT 8, "MS Shell Dlg", 400, 0, 0 66 | { 67 | CONTROL "&Enable text editor", IDC_TEXT_EDITOR_ENABLED, "Button", BS_AUTOCHECKBOX|WS_TABSTOP, 7, 7, 77, 14 68 | CONTROL "Behavior", -1, "Button", BS_GROUPBOX, 7, 28, 70, 56 69 | CONTROL "Auto-i&ndent", IDC_TEXT_AUTO_INDENT, "Button", BS_AUTOCHECKBOX|WS_TABSTOP, 14, 42, 56, 14 70 | CONTROL "Display", -1, "Button", BS_GROUPBOX, 84, 28, 140, 56 71 | CONTROL "&Font...", IDC_TEXT_FONT, "Button", WS_TABSTOP, 91, 42, 35, 14 72 | CONTROL "", IDC_TEXT_FONT_NAME, "Static", WS_GROUP, 133, 45, 84, 8 73 | CONTROL "&Tabs:", -1, "Static", SS_RIGHT|WS_GROUP, 91, 66, 21, 8 74 | CONTROL "", IDC_TEXT_TAB_SIZE, "Edit", ES_NUMBER|WS_BORDER|WS_TABSTOP, 119, 63, 21, 14 75 | CONTROL "", IDC_TEXT_TAB_SIZE_UD, "msctls_updown32", UDS_AUTOBUDDY|UDS_SETBUDDYINT|UDS_ARROWKEYS, 140, 63, 14, 14 76 | CONTROL "Save format", -1, "Button", BS_GROUPBOX, 7, 91, 217, 49 77 | CONTROL "&Line endings", -1, "Static", SS_RIGHT|WS_GROUP, 14, 108, 42, 8 78 | CONTROL "", IDC_TEXT_NEWLINES, "ComboBox", WS_BORDER|CBS_DROPDOWNLIST|CBS_HASSTRINGS|WS_VSCROLL|WS_TABSTOP, 63, 105, 77, 300 79 | CONTROL "Auto-detect", IDC_TEXT_AUTO_NEWLINES, "Button", BS_AUTOCHECKBOX|WS_TABSTOP, 154, 105, 56, 14 80 | CONTROL "En&coding", -1, "Static", SS_RIGHT|WS_GROUP, 14, 122, 42, 8 81 | CONTROL "", IDC_TEXT_ENCODING, "ComboBox", WS_BORDER|CBS_DROPDOWNLIST|CBS_HASSTRINGS|WS_VSCROLL|WS_TABSTOP, 63, 119, 77, 300 82 | CONTROL "Auto-detect", IDC_TEXT_AUTO_ENCODING, "Button", BS_AUTOCHECKBOX|WS_TABSTOP, 154, 119, 56, 14 83 | CONTROL "&Scratch folder", -1, "Button", BS_GROUPBOX, 7, 147, 217, 56 84 | CONTROL "", IDC_SCRATCH_FOLDER_PATH, "ComboBoxEx32", CBS_DROPDOWN|WS_TABSTOP|0x00000040, 14, 161, 140, 300 85 | CONTROL "&Browse...", IDC_SCRATCH_FOLDER_BROWSE, "Button", WS_TABSTOP, 161, 161, 56, 14 86 | CONTROL "File name:", -1, "Static", WS_GROUP, 14, 185, 35, 8 87 | CONTROL "", IDC_SCRATCH_FILE_NAME, "Edit", WS_BORDER|WS_TABSTOP, 56, 182, 161, 14 88 | } 89 | 90 | IDD_SETTINGS_BROWSER DIALOGEX DISCARDABLE 0, 0, 231, 217 91 | STYLE DS_SHELLFONT|WS_CHILD|WS_CAPTION|WS_DISABLED 92 | CAPTION "Default Browser" 93 | FONT 8, "MS Shell Dlg", 400, 0, 0 94 | { 95 | CONTROL 32516, -1, "Static", SS_ICON, 14, 7, 0, 0 96 | CONTROL "Replace File Explorer as the default program for opening all folders.", -1, "Static", WS_GROUP, 42, 8, 175, 16 97 | CONTROL "&Set as default browser", IDC_SET_DEFAULT_BROWSER, "Button", WS_TABSTOP, 7, 35, 84, 14 98 | CONTROL "&Reset default browser", IDC_RESET_DEFAULT_BROWSER, "Button", WS_TABSTOP, 7, 56, 84, 14 99 | } 100 | 101 | IDD_SETTINGS_ABOUT DIALOGEX DISCARDABLE 0, 0, 231, 217 102 | STYLE DS_SHELLFONT|WS_CHILD|WS_CAPTION|WS_DISABLED 103 | CAPTION "Update/About" 104 | FONT 8, "MS Shell Dlg", 400, 0, 0 105 | { 106 | CONTROL IDR_APP_ICON, -1, "Static", SS_ICON, 7, 7, 0, 0 107 | CONTROL "ChromaFiler", 12, "Static", SS_NOTIFY|WS_GROUP, 35, 7, 49, 8 108 | CONTROL "Version:", -1, "Static", WS_GROUP, 35, 15, 28, 8 109 | CONTROL "", IDC_VERSION, "Static", WS_GROUP, 70, 15, 28, 8 110 | CONTROL "&Help", IDC_HELP_LINK, "Button", WS_TABSTOP, 7, 28, 63, 14 111 | CONTROL "&Website", IDC_WEBSITE_LINK, "Button", WS_TABSTOP, 84, 28, 63, 14 112 | CONTROL "&Donate", IDC_DONATE_LINK, "Button", WS_TABSTOP, 161, 28, 63, 14 113 | CONTROL "&Check for update", IDC_UPDATES_LINK, "Button", WS_TABSTOP, 7, 49, 63, 14 114 | CONTROL "A&utomatically check for updates weekly", IDC_AUTO_UPDATE, "Button", BS_AUTOCHECKBOX|WS_TABSTOP, 77, 49, 147, 14 115 | CONTROL "", IDC_LEGAL_INFO, "Edit", ES_MULTILINE|ES_READONLY|WS_VSCROLL|WS_BORDER|WS_TABSTOP, 7, 70, 217, 138 116 | } 117 | 118 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | #include "FolderWindow.h" 3 | #include "ThumbnailView.h" 4 | #include "PreviewWindow.h" 5 | #include "TextWindow.h" 6 | #include "TrayWindow.h" 7 | #include "CreateItemWindow.h" 8 | #include "Settings.h" 9 | #include "SettingsDialog.h" 10 | #include "Update.h" 11 | #include "ExecuteCommand.h" 12 | #include "DPI.h" 13 | #include "UIStrings.h" 14 | #include "WinUtils.h" 15 | #include 16 | #include 17 | 18 | #pragma comment(lib, "Dwmapi.lib") 19 | #pragma comment(lib, "Gdi32.lib") 20 | #pragma comment(lib, "UxTheme.lib") 21 | #pragma comment(lib, "Comctl32.lib") 22 | #pragma comment(lib, "Comdlg32.lib") 23 | #pragma comment(lib, "Wininet.lib") 24 | 25 | using namespace chromafiler; 26 | 27 | const wchar_t SHELL_PREFIX[] = L"shell:"; 28 | 29 | enum LaunchType {LAUNCH_FAIL, LAUNCH_HEADLESS, LAUNCH_AUTO, LAUNCH_FOUND, LAUNCH_TRAY, LAUNCH_TEXT}; 30 | 31 | #ifdef CHROMAFILER_DEBUG 32 | int main(int, char**) { 33 | return wWinMain(nullptr, nullptr, nullptr, SW_SHOWNORMAL); 34 | } 35 | 36 | bool logHRESULT(long hr, const char *file, int line, const char *expr) { 37 | if (SUCCEEDED(hr)) 38 | return true; 39 | local_wstr_ptr message = getErrorMessage(hr); 40 | debugPrintf(L"Error 0x%X: %s\n in %S (%S:%d)\n", hr, message.get(), expr, file, line); 41 | return false; 42 | } 43 | 44 | void logLastError(const char *file, int line, const char *expr) { 45 | DWORD error = GetLastError(); 46 | local_wstr_ptr message = getErrorMessage(error); 47 | debugPrintf(L"Error %d: %s\n in %S (%S:%d)\n", error, message.get(), expr, file, line); 48 | } 49 | #endif 50 | 51 | LaunchType createWindowFromCommandLine(int argc, wchar_t **argv, int showCommand); 52 | DWORD WINAPI checkLastVersion(void *); 53 | void showWelcomeDialog(); 54 | HRESULT WINAPI welcomeDialogCallback(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR data); 55 | void startTrayProcess(); 56 | DWORD WINAPI updateJumpList(void *); 57 | DWORD WINAPI recoveryCallback(void *); 58 | 59 | int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int showCommand) { 60 | OutputDebugString(L"hiiiii ^w^\n"); // DO NOT REMOVE!! 61 | 62 | #ifdef CHROMAFILER_MEMLEAKS 63 | _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); 64 | _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_WNDW); 65 | debugPrintf(L"Compiled with memory leak detection\n"); 66 | #endif 67 | 68 | if (!checkHR(OleInitialize(0))) // needed for drag/drop 69 | return 0; 70 | 71 | INITCOMMONCONTROLSEX controls = {sizeof(controls)}; 72 | controls.dwICC = ICC_STANDARD_CLASSES | ICC_BAR_CLASSES | ICC_USEREX_CLASSES; 73 | InitCommonControlsEx(&controls); 74 | 75 | if (settings::getAdminWarning()) { 76 | TOKEN_ELEVATION elevation = {}; 77 | HANDLE procToken; 78 | if (checkLE(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &procToken))) { 79 | DWORD size = sizeof(elevation); 80 | checkLE(GetTokenInformation(procToken, TokenElevation, 81 | &elevation, sizeof(elevation), &size)); 82 | CloseHandle(procToken); 83 | } 84 | if (elevation.TokenIsElevated) { 85 | TASKDIALOGCONFIG config = {sizeof(config)}; 86 | config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION; 87 | config.dwCommonButtons = TDCBF_YES_BUTTON | TDCBF_NO_BUTTON; 88 | config.pszWindowTitle = MAKEINTRESOURCE(IDS_APP_NAME); 89 | config.pszMainIcon = TD_WARNING_ICON; 90 | config.pszContent = MAKEINTRESOURCE(IDS_ADMIN_WARNING); 91 | config.nDefaultButton = IDNO; 92 | config.pszVerificationText = MAKEINTRESOURCE(IDS_DONT_ASK); 93 | config.pfCallback = [](HWND hwnd, UINT msg, WPARAM, LPARAM, LONG_PTR) -> HRESULT { 94 | if (msg == TDN_CREATED) { 95 | SendMessage(hwnd, WM_SETICON, ICON_BIG, 0); // this is what TaskDialog does 96 | SendMessage(hwnd, WM_SETICON, ICON_SMALL, 0); 97 | } 98 | return S_OK; 99 | }; 100 | int result = 0; 101 | BOOL dontShowAgain = false; 102 | checkHR(TaskDialogIndirect(&config, &result, nullptr, &dontShowAgain)); 103 | if (result != IDYES) 104 | return 0; 105 | if (dontShowAgain) // only counts if user chose yes! 106 | settings::setAdminWarning(false); 107 | } 108 | } 109 | 110 | initDPI(); 111 | ItemWindow::init(); 112 | FolderWindow::init(); 113 | ThumbnailView::init(); 114 | PreviewWindow::init(); 115 | TextWindow::init(); 116 | TrayWindow::init(); 117 | 118 | // https://docs.microsoft.com/en-us/windows/win32/shell/appids 119 | checkHR(SetCurrentProcessExplicitAppUserModelID(APP_ID)); 120 | 121 | debugPrintf(L"%s\n", GetCommandLine()); 122 | int argc; 123 | wchar_t **argv = CommandLineToArgvW(GetCommandLine(), &argc); 124 | LaunchType type = createWindowFromCommandLine(argc, argv, showCommand); 125 | LocalFree(argv); 126 | if (type == LAUNCH_FAIL || type == LAUNCH_FOUND) 127 | return 0; 128 | 129 | CFExecuteFactory executeFactory(false), executeTextFactory(true); 130 | HRESULT regHR = E_FAIL, regTextHR = E_FAIL; 131 | DWORD regCookie = 0, regTextCookie = 0; 132 | if (type == LAUNCH_TRAY) { 133 | checkHR(RegisterApplicationRecoveryCallback(recoveryCallback, nullptr, 134 | RECOVERY_DEFAULT_PING_INTERVAL, 0)); 135 | } else { 136 | // https://devblogs.microsoft.com/oldnewthing/20100503-00/?p=14183 137 | regHR = CoRegisterClassObject(CLSID_CFExecute, &executeFactory, 138 | CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, ®Cookie); 139 | checkHR(regHR); 140 | regTextHR = checkHR(CoRegisterClassObject(CLSID_CFExecuteText, &executeTextFactory, 141 | CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, ®TextCookie)); 142 | checkHR(regTextHR); 143 | } 144 | 145 | HANDLE jumpListThread = nullptr, versionThread = nullptr; 146 | SHCreateThreadWithHandle(updateJumpList, nullptr, CTF_COINIT_STA, nullptr, &jumpListThread); 147 | SHCreateThreadWithHandle(checkLastVersion, nullptr, 0, nullptr, &versionThread); 148 | 149 | autoUpdateCheck(); 150 | 151 | MSG msg; 152 | while (GetMessage(&msg, nullptr, 0, 0)) { 153 | #ifdef CHROMAFILER_DEBUG 154 | ULONGLONG tick = GetTickCount64(); 155 | #endif 156 | if (handleSettingsDialogMessage(&msg)) 157 | continue; 158 | if (ItemWindow::activeWindow && ItemWindow::activeWindow->handleTopLevelMessage(&msg)) 159 | continue; 160 | TranslateMessage(&msg); 161 | DispatchMessage(&msg); 162 | #ifdef CHROMAFILER_DEBUG 163 | ULONGLONG time = GetTickCount64() - tick; 164 | if (time > 100 && msg.message != WM_NCLBUTTONDOWN) { 165 | wchar_t className[64] = L"Unknown Class"; 166 | GetClassName(msg.hwnd, className, _countof(className)); 167 | debugPrintf(L"%s took %lld ms to handle 0x%04X!\n", className, time, msg.message); 168 | } 169 | #endif 170 | } 171 | 172 | if (SUCCEEDED(regHR)) 173 | checkHR(CoRevokeClassObject(regCookie)); 174 | if (SUCCEEDED(regTextHR)) 175 | checkHR(CoRevokeClassObject(regTextCookie)); 176 | 177 | WaitForSingleObject(jumpListThread, INFINITE); 178 | WaitForSingleObject(versionThread, INFINITE); 179 | checkLE(CloseHandle(jumpListThread)); 180 | checkLE(CloseHandle(versionThread)); 181 | waitForUpdateThread(); 182 | 183 | ItemWindow::uninit(); 184 | ThumbnailView::uninit(); 185 | PreviewWindow::uninit(); 186 | OleUninitialize(); 187 | 188 | #ifdef CHROMAFILER_MEMLEAKS 189 | if (MEMLEAK_COUNT) { 190 | auto message = format(L"Detected memory leak (count: %1!d!)", MEMLEAK_COUNT); 191 | MessageBox(nullptr, message.get(), L"Memory leak!", MB_ICONERROR); 192 | } 193 | #endif 194 | debugPrintf(L"Goodbye\n"); 195 | return (int)msg.wParam; 196 | } 197 | 198 | LaunchType createWindowFromCommandLine(int argc, wchar_t **argv, int showCommand) { 199 | wstr_ptr pathAlloc; 200 | wchar_t *path = nullptr; 201 | LaunchType type = LAUNCH_AUTO; 202 | bool scratch = false; 203 | for (int i = 1; i < argc; i++) { 204 | wchar_t *arg = argv[i]; 205 | if (lstrcmpi(arg, L"/tray") == 0) { 206 | type = LAUNCH_TRAY; 207 | scratch = false; 208 | } else if (lstrcmpi(arg, L"/scratch") == 0) { // deprecated 209 | type = LAUNCH_TEXT; 210 | scratch = true; 211 | } else if (lstrcmpi(arg, L"/text") == 0) { 212 | type = LAUNCH_TEXT; 213 | } else if (lstrcmpi(arg, L"/embedding") == 0 || lstrcmpi(arg, L"-embedding") == 0) { 214 | // https://devblogs.microsoft.com/oldnewthing/20100503-00/?p=14183 215 | debugPrintf(L"Started as local COM server\n"); 216 | return LAUNCH_HEADLESS; 217 | } else if (lstrcmpi(arg, L"/test") == 0) { 218 | #ifdef CHROMAFILER_DEBUG 219 | settings::testMode = true; 220 | #else 221 | return LAUNCH_FAIL; 222 | #endif 223 | } else if (!path) { 224 | int argLen = lstrlen(arg); 225 | if (arg[argLen - 1] == '"') 226 | arg[argLen - 1] = '\\'; // fix weird CommandLineToArgvW behavior with \" 227 | 228 | if ((argLen >= 1 && arg[0] == ':') || (argLen >= _countof(SHELL_PREFIX) 229 | && _memicmp(arg, SHELL_PREFIX, _countof(SHELL_PREFIX)) == 0)) { 230 | path = arg; // assume desktop absolute parsing name 231 | } else { // assume relative file system path 232 | pathAlloc = wstr_ptr(new wchar_t[MAX_PATH]); 233 | path = pathAlloc.get(); 234 | if (!GetFullPathName(arg, MAX_PATH, path, nullptr)) 235 | path = arg; 236 | } 237 | } 238 | } 239 | 240 | if (!path) { 241 | if (scratch) { 242 | pathAlloc = settings::getScratchFolder(); 243 | } else if (type == LAUNCH_TEXT) { 244 | pathAlloc = settings::getScratchFolder(); 245 | scratch = true; 246 | } else if (type == LAUNCH_TRAY) { 247 | pathAlloc = settings::getTrayFolder(); 248 | } else { 249 | pathAlloc = settings::getStartingFolder(); 250 | } 251 | path = pathAlloc.get(); 252 | } 253 | 254 | CComPtr startItem = itemFromPath(path); 255 | if (!startItem) 256 | return LAUNCH_FAIL; 257 | startItem = resolveLink(startItem); 258 | if (scratch) 259 | startItem = createScratchFile(startItem); 260 | if (!startItem) 261 | return LAUNCH_FAIL; 262 | 263 | if (!scratch && type != LAUNCH_TRAY) { 264 | CComPtr shellWindows; 265 | shellWindows.CoCreateInstance(CLSID_ShellWindows); 266 | if (shellWindows && showItemWindow(startItem, shellWindows, showCommand)) 267 | return LAUNCH_FOUND; 268 | } 269 | 270 | CComPtr initialWindow; 271 | if (type == LAUNCH_TRAY) { 272 | initialWindow.Attach(new TrayWindow(nullptr, startItem)); 273 | } else if (type == LAUNCH_TEXT) { 274 | initialWindow.Attach(new TextWindow(nullptr, startItem)); 275 | } else { 276 | initialWindow = createItemWindow(nullptr, startItem); 277 | } 278 | initialWindow->setScratch(scratch); 279 | if (scratch) 280 | initialWindow->resetViewState(); 281 | // TODO can we get the monitor passed to ShellExecuteEx? 282 | initialWindow->create(initialWindow->requestedRect(nullptr), showCommand); 283 | return type; 284 | } 285 | 286 | DWORD WINAPI checkLastVersion(void *) { 287 | DWORD lastVersion = settings::getLastOpenedVersion(); 288 | DWORD curVersion = makeVersion(CHROMAFILER_VERSION); 289 | if (lastVersion != curVersion) { 290 | settings::setLastOpenedVersion(curVersion); 291 | if (lastVersion == settings::DEFAULT_LAST_OPENED_VERSION) 292 | showWelcomeDialog(); 293 | } 294 | return 0; 295 | } 296 | 297 | void showWelcomeDialog() { 298 | TASKDIALOGCONFIG config = {sizeof(config)}; 299 | config.hInstance = GetModuleHandle(nullptr); 300 | config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_USE_COMMAND_LINKS 301 | | TDF_VERIFICATION_FLAG_CHECKED; 302 | config.dwCommonButtons = TDCBF_CLOSE_BUTTON; 303 | config.pszWindowTitle = MAKEINTRESOURCE(IDS_APP_NAME); 304 | config.pszMainIcon = MAKEINTRESOURCE(IDR_APP_ICON); 305 | config.pszMainInstruction = MAKEINTRESOURCE(IDS_WELCOME_HEADER); 306 | config.pszContent = MAKEINTRESOURCE(IDS_WELCOME_BODY); 307 | TASKDIALOG_BUTTON buttons[] = { 308 | {IDS_WELCOME_TUTORIAL, MAKEINTRESOURCE(IDS_WELCOME_TUTORIAL)}, 309 | {IDS_WELCOME_TRAY, MAKEINTRESOURCE(IDS_WELCOME_TRAY)}, 310 | {IDS_WELCOME_BROWSER, MAKEINTRESOURCE(IDS_WELCOME_BROWSER)}}; 311 | config.cButtons = _countof(buttons); 312 | config.pButtons = buttons; 313 | config.nDefaultButton = IDCLOSE; 314 | config.pszVerificationText = MAKEINTRESOURCE(IDS_WELCOME_UPDATE); 315 | config.pfCallback = welcomeDialogCallback; 316 | 317 | BOOL autoUpdateChecked; 318 | if (checkHR(TaskDialogIndirect(&config, nullptr, nullptr, &autoUpdateChecked))) 319 | settings::setUpdateCheckEnabled(autoUpdateChecked); 320 | } 321 | 322 | HRESULT WINAPI welcomeDialogCallback(HWND hwnd, UINT msg, WPARAM wParam, LPARAM, LONG_PTR) { 323 | if (msg == TDN_BUTTON_CLICKED && wParam == IDS_WELCOME_TUTORIAL) { 324 | ShellExecute(nullptr, L"open", L"https://github.com/vanjac/chromafiler/wiki/Tutorial", 325 | nullptr, nullptr, SW_SHOWNORMAL); 326 | return S_FALSE; 327 | } else if (msg == TDN_BUTTON_CLICKED && wParam == IDS_WELCOME_TRAY) { 328 | if (!TrayWindow::findTray()) { 329 | startTrayProcess(); 330 | } 331 | settings::setTrayOpenOnStartup(true); 332 | return S_FALSE; 333 | } else if (msg == TDN_BUTTON_CLICKED && wParam == IDS_WELCOME_BROWSER) { 334 | HINSTANCE instance = GetModuleHandle(nullptr); 335 | if (!settings::supportsDefaultBrowser()) { 336 | TaskDialog(hwnd, instance, MAKEINTRESOURCE(IDS_BROWSER_SET_FAILED), 337 | nullptr, MAKEINTRESOURCE(IDS_REQUIRE_CONTEXT), 0, TD_ERROR_ICON, nullptr); 338 | } else { 339 | int result = 0; 340 | TaskDialog(hwnd, instance, MAKEINTRESOURCE(IDS_CONFIRM_CAPTION), 341 | nullptr, MAKEINTRESOURCE(IDS_BROWSER_SET_CONFIRM), 342 | TDCBF_YES_BUTTON | TDCBF_CANCEL_BUTTON, TD_WARNING_ICON, &result); 343 | if (result == IDYES) { 344 | settings::setDefaultBrowser(true); 345 | TaskDialog(GetParent(hwnd), instance, MAKEINTRESOURCE(IDS_SUCCESS_CAPTION), 346 | nullptr, MAKEINTRESOURCE(IDS_BROWSER_SET), 0, nullptr, nullptr); 347 | } 348 | } 349 | return S_FALSE; 350 | } 351 | return S_OK; 352 | } 353 | 354 | void startTrayProcess() { 355 | wchar_t exePath[MAX_PATH]; 356 | if (checkLE(GetModuleFileName(nullptr, exePath, _countof(exePath)))) { 357 | STARTUPINFO startup = {sizeof(startup)}; 358 | PROCESS_INFORMATION info = {}; 359 | checkLE(CreateProcess(exePath, L"ChromaFiler.exe /tray", nullptr, nullptr, FALSE, 360 | DETACHED_PROCESS, nullptr, nullptr, &startup, &info)); 361 | checkLE(CloseHandle(info.hProcess)); 362 | checkLE(CloseHandle(info.hThread)); 363 | } 364 | } 365 | 366 | DWORD WINAPI updateJumpList(void *) { 367 | CComPtr jumpList; 368 | if (!checkHR(jumpList.CoCreateInstance(__uuidof(DestinationList)))) 369 | return 0; 370 | checkHR(jumpList->SetAppID(APP_ID)); 371 | UINT minSlots; 372 | CComPtr removedDestinations; 373 | checkHR(jumpList->BeginList(&minSlots, IID_PPV_ARGS(&removedDestinations))); 374 | // previous versions had a jump list, this will clear it 375 | checkHR(jumpList->CommitList()); 376 | return 0; 377 | } 378 | 379 | DWORD WINAPI recoveryCallback(void *) { 380 | BOOL cancelled; 381 | ApplicationRecoveryInProgress(&cancelled); 382 | startTrayProcess(); 383 | ApplicationRecoveryFinished(true); 384 | return 0; 385 | } 386 | 387 | namespace chromafiler { 388 | // main.h 389 | #ifdef CHROMAFILER_MEMLEAKS 390 | long MEMLEAK_COUNT = 0; 391 | #endif 392 | 393 | static long lockCount = 0; 394 | 395 | void lockProcess() { 396 | InterlockedIncrement(&lockCount); 397 | } 398 | 399 | void unlockProcess() { 400 | if (InterlockedDecrement(&lockCount) == 0) 401 | PostQuitMessage(0); 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace chromafiler { 5 | 6 | const wchar_t APP_ID[] = L"chroma.file"; 7 | 8 | // app exits when all windows are closed 9 | void lockProcess(); 10 | void unlockProcess(); 11 | 12 | } // namespace 13 | -------------------------------------------------------------------------------- /src/res/ChromaFiler.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | true 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/res/SegMDL2-subset.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanjac/chromafiler/fde6bd6a8a80a749c8565822f581500c927494ab/src/res/SegMDL2-subset.ttf -------------------------------------------------------------------------------- /src/res/adwaita/COPYING: -------------------------------------------------------------------------------- 1 | This work is licenced under the terms of either the GNU LGPL v3 or 2 | Creative Commons Attribution-Share Alike 3.0 United States License. 3 | 4 | To view a copy of the CC-BY-SA licence, visit 5 | http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative 6 | Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA. 7 | 8 | When attributing the artwork, using "GNOME Project" is enough. 9 | Please link to http://www.gnome.org where available. 10 | 11 | -------------------------------------------------------------------------------- /src/res/adwaita/right_side.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanjac/chromafiler/fde6bd6a8a80a749c8565822f581500c927494ab/src/res/adwaita/right_side.cur -------------------------------------------------------------------------------- /src/res/folder.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanjac/chromafiler/fde6bd6a8a80a749c8565822f581500c927494ab/src/res/folder.ico -------------------------------------------------------------------------------- /src/res/subset.txt: -------------------------------------------------------------------------------- 1 | Segoe MDL2 Assets font subset generated with https://everythingfonts.com/subsetter 2 | Included characters:  3 | U+E973 (ChevronLeftMed) 4 | U+E72C (Refresh) 5 | U+E712 (More) 6 | U+E948 (CalculatorAddition) 7 | U+E890 (View) 8 | U+E74E (Save) 9 | U+E74D (Delete) 10 | 11 | https://docs.microsoft.com/en-us/windows/apps/design/style/segoe-ui-symbol-font -------------------------------------------------------------------------------- /src/res/text.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanjac/chromafiler/fde6bd6a8a80a749c8565822f581500c927494ab/src/res/text.ico -------------------------------------------------------------------------------- /src/resource.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "dialog.h" 3 | 4 | #define CHROMAFILER_VERSION 0,7,1,0 5 | #define CHROMAFILER_VERSION_STRING "0.7.1\0" 6 | 7 | #define IDR_RT_MANIFEST1 1 // must be 1 for an exe 8 | 9 | #define IDR_ITEM_ACCEL 102 10 | #define IDR_ITEM_MENU 111 11 | #define IDM_NEXT_WINDOW 1001 12 | #define IDM_PREV_WINDOW 1002 13 | #define IDM_CLOSE_WINDOW 1003 14 | #define IDM_REFRESH 1004 15 | #define IDM_HELP 1005 16 | #define IDM_PROXY_MENU 1006 17 | #define IDM_NEW_FOLDER 1007 18 | #define IDM_RENAME_PROXY 1008 19 | #define IDM_SETTINGS 1009 20 | #define IDM_PARENT_MENU 1010 21 | #define IDM_NEW_ITEM_MENU 1011 22 | #define IDM_VIEW_MENU 1012 23 | #define IDM_DETACH 1013 24 | #define IDM_CLOSE_PARENT 1014 25 | #define IDM_NEW_TEXT_FILE 1015 26 | #define IDM_DELETE_PROXY 1016 27 | #define IDM_DEBUG_NAMES 1017 28 | #define IDM_CONTEXT_MENU 1018 29 | #define IDM_PROXY_BUTTON 1019 30 | 31 | #define IDM_SHELL_FIRST 0x4000 32 | #define IDM_SHELL_LAST 0x7FFF 33 | 34 | #define IDR_TEXT_ACCEL 107 35 | #define IDM_SAVE 1100 36 | #define IDM_FIND 1101 37 | #define IDM_FIND_NEXT 1102 38 | #define IDM_FIND_PREV 1103 39 | #define IDM_REPLACE 1104 40 | #define IDM_WORD_WRAP 1105 41 | #define IDM_ZOOM_IN 1106 42 | #define IDM_ZOOM_OUT 1107 43 | #define IDM_ZOOM_RESET 1108 44 | #define IDM_LINE_SELECT 1109 45 | 46 | #define IDR_TEXT_MENU 108 47 | #define IDM_UNDO 1200 48 | #define IDM_REDO 1201 49 | #define IDM_CUT 1202 50 | #define IDM_COPY 1203 51 | #define IDM_PASTE 1204 52 | #define IDM_DELETE 1205 53 | #define IDM_SELECT_ALL 1206 54 | 55 | #define IDR_ICON_FONT 103 56 | // https://docs.microsoft.com/en-us/windows/apps/design/style/segoe-ui-symbol-font 57 | #define MDL2_CHEVRON_LEFT_MED L"\uE973" 58 | #define MDL2_REFRESH L"\uE72C" 59 | #define MDL2_MORE L"\uE712" 60 | #define MDL2_CALCULATOR_ADDITION L"\uE948" 61 | #define MDL2_VIEW L"\uE890" 62 | #define MDL2_SAVE L"\uE74E" 63 | #define MDL2_DELETE L"\uE74D" 64 | 65 | #define IDC_RIGHT_SIDE 150 66 | 67 | #define IDS_SETTINGS_CAPTION 200 68 | #define IDS_MENU_COMMAND 201 69 | #define IDS_REFRESH_COMMAND 202 70 | #define IDS_NEW_ITEM_COMMAND 203 71 | #define IDS_VIEW_COMMAND 204 72 | #define IDS_SAVE_COMMAND 205 73 | #define IDS_DELETE_COMMAND 206 74 | #define IDS_UNSAVED_CAPTION 207 75 | #define IDS_SAVE_BUTTON 208 76 | #define IDS_DONT_SAVE_BUTTON 209 77 | #define IDS_DELETE_BUTTON 210 78 | #define IDS_SUCCESS_CAPTION 211 79 | #define IDS_BROWSER_SET_FAILED 212 80 | #define IDS_BROWSER_SET 213 81 | #define IDS_BROWSER_RESET 214 82 | #define IDS_REQUIRE_CONTEXT 215 83 | #define IDS_BROWSER_SET_CONFIRM 216 84 | #define IDS_APP_NAME 217 85 | #define IDS_WELCOME_HEADER 218 86 | #define IDS_WELCOME_BODY 219 87 | #define IDS_WELCOME_TUTORIAL 220 88 | #define IDS_WELCOME_TRAY 221 89 | #define IDS_WELCOME_BROWSER 222 90 | #define IDS_CONFIRM_CAPTION 223 91 | #define IDS_NO_UPDATE_CAPTION 224 92 | #define IDS_NO_UPDATE 225 93 | #define IDS_UPDATE_ERROR 226 94 | #define IDS_WELCOME_UPDATE 227 95 | #define IDS_OPEN_PARENT_COMMAND 228 96 | #define IDS_SAVE_ERROR 229 97 | #define IDS_ERROR_CAPTION 230 98 | #define IDS_CHROMATEXT 231 99 | #define IDS_TEXT_CANT_FIND 232 100 | #define IDS_UPDATE_NOTIF_TITLE 233 101 | #define IDS_UPDATE_NOTIF_INFO 234 102 | #define IDS_CANT_FIND_ITEM 235 103 | #define IDS_FOLDER_STATUS 236 104 | #define IDS_FOLDER_STATUS_SEL 237 105 | #define IDS_SAVE_PROMPT 238 106 | #define IDS_DELETE_PROMPT 239 107 | #define IDS_TEXT_STATUS 240 108 | #define IDS_TEXT_STATUS_SEL 241 109 | #define IDS_TEXT_STATUS_REPLACE 242 110 | #define IDS_TEXT_UNDO 243 111 | #define IDS_TEXT_REDO 244 112 | #define IDS_FONT_NAME 245 113 | #define IDS_UNKNOWN_ERROR 246 114 | #define IDS_LEGAL_INFO 247 115 | #define IDS_FOLDER_ERROR 248 116 | #define IDS_TEXT_LOADING 249 117 | #define IDS_INVALID_CHARS 250 118 | #define IDS_ADMIN_WARNING 251 119 | #define IDS_DONT_ASK 252 120 | 121 | // corresponds to UNDONAMEID 122 | #define IDS_TEXT_UNDO_UNKNOWN 300 123 | #define IDS_TEXT_UNDO_TYPING 301 124 | #define IDS_TEXT_UNDO_DELETE 302 125 | #define IDS_TEXT_UNDO_DRAGDROP 303 126 | #define IDS_TEXT_UNDO_CUT 304 127 | #define IDS_TEXT_UNDO_PASTE 305 128 | #define IDS_TEXT_UNDO_AUTOTABLE 306 129 | 130 | #define IDS_NEWLINES_CRLF 320 131 | #define IDS_NEWLINES_LF 321 132 | #define IDS_NEWLINES_CR 322 133 | #define IDS_NEWLINES_COUNT 3 134 | 135 | #define IDS_ENCODING_UTF8 330 136 | #define IDS_ENCODING_UTF8BOM 331 137 | #define IDS_ENCODING_UTF16LE 332 138 | #define IDS_ENCODING_UTF16BE 333 139 | #define IDS_ENCODING_ANSI 334 140 | #define IDS_ENCODING_COUNT 5 141 | -------------------------------------------------------------------------------- /src/resource.rc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "resource.h" 3 | 4 | LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT 5 | 6 | 1 VERSIONINFO 7 | FILEVERSION CHROMAFILER_VERSION 8 | PRODUCTVERSION CHROMAFILER_VERSION 9 | FILEOS VOS_NT_WINDOWS32 10 | FILETYPE VFT_APP 11 | { 12 | BLOCK "StringFileInfo" { 13 | BLOCK "040904B0" { 14 | VALUE "CompanyName", "chroma zone\0" 15 | VALUE "FileDescription", "ChromaFiler\0" 16 | VALUE "FileVersion", CHROMAFILER_VERSION_STRING 17 | VALUE "LegalCopyright", "Copyright (c) J. van't Hoog\0" 18 | VALUE "ProductName", "ChromaFiler\0" 19 | VALUE "ProductVersion", CHROMAFILER_VERSION_STRING 20 | } 21 | } 22 | BLOCK "VarFileInfo" { 23 | VALUE "Translation", 0x0409, 0x04B0 24 | } 25 | } 26 | 27 | IDR_RT_MANIFEST1 RT_MANIFEST "res\\ChromaFiler.exe.manifest" 28 | 29 | IDR_APP_ICON ICON "res\\folder.ico" 30 | 31 | IDC_RIGHT_SIDE CURSOR "res\\adwaita\\right_side.cur" 32 | IDR_ICON_FONT FONT "res\\SegMDL2-subset.ttf" 33 | 34 | IDR_ITEM_ACCEL ACCELERATORS { 35 | VK_TAB, IDM_NEXT_WINDOW, VIRTKEY 36 | VK_DOWN, IDM_NEXT_WINDOW, VIRTKEY, ALT 37 | VK_TAB, IDM_PREV_WINDOW, VIRTKEY, SHIFT 38 | VK_UP, IDM_PREV_WINDOW, VIRTKEY, ALT 39 | VK_OEM_2, IDM_DETACH, VIRTKEY, CONTROL 40 | VK_OEM_2, IDM_CLOSE_PARENT, VIRTKEY, CONTROL, SHIFT 41 | "W", IDM_CLOSE_WINDOW, VIRTKEY, CONTROL 42 | VK_F5, IDM_REFRESH, VIRTKEY 43 | "R", IDM_REFRESH, VIRTKEY, CONTROL 44 | VK_F1, IDM_HELP, VIRTKEY 45 | VK_OEM_COMMA, IDM_SETTINGS, VIRTKEY, CONTROL 46 | VK_TAB, IDM_PARENT_MENU, VIRTKEY, CONTROL, SHIFT 47 | VK_F1, IDM_DEBUG_NAMES, VIRTKEY, CONTROL 48 | // proxy operations 49 | VK_F10, IDM_PROXY_MENU, VIRTKEY 50 | VK_F2, IDM_RENAME_PROXY, VIRTKEY, SHIFT 51 | "D", IDM_DELETE_PROXY, VIRTKEY, CONTROL, SHIFT 52 | // folder 53 | "N", IDM_NEW_FOLDER, VIRTKEY, CONTROL, SHIFT 54 | "N", IDM_NEW_TEXT_FILE, VIRTKEY, CONTROL 55 | } 56 | 57 | IDR_TEXT_ACCEL ACCELERATORS { 58 | "S", IDM_SAVE, VIRTKEY, CONTROL 59 | "F", IDM_FIND, VIRTKEY, CONTROL 60 | VK_F3, IDM_FIND_NEXT, VIRTKEY 61 | VK_F3, IDM_FIND_PREV, VIRTKEY, SHIFT 62 | "H", IDM_REPLACE, VIRTKEY, CONTROL 63 | VK_OEM_PLUS, IDM_ZOOM_IN, VIRTKEY, CONTROL 64 | VK_OEM_MINUS, IDM_ZOOM_OUT, VIRTKEY, CONTROL 65 | "0", IDM_ZOOM_RESET, VIRTKEY, CONTROL 66 | "L", IDM_LINE_SELECT, VIRTKEY, CONTROL 67 | "W", IDM_WORD_WRAP, VIRTKEY, CONTROL, SHIFT 68 | } 69 | 70 | IDR_ITEM_MENU MENU { 71 | MENUITEM "ChromaFiler Settings\tCtrl+,", IDM_SETTINGS 72 | MENUITEM "Help\tF1", IDM_HELP 73 | } 74 | 75 | IDR_TEXT_MENU MENU { 76 | POPUP "" { 77 | // File 78 | MENUITEM "&Save\tCtrl+S", IDM_SAVE 79 | MENUITEM SEPARATOR 80 | // Edit 81 | MENUITEM "&Undo\tCtrl+Z", IDM_UNDO 82 | MENUITEM "&Redo\tCtrl+Y", IDM_REDO 83 | MENUITEM SEPARATOR 84 | MENUITEM "Cu&t\tCtrl+X", IDM_CUT 85 | MENUITEM "&Copy\tCtrl+C", IDM_COPY 86 | MENUITEM "&Paste\tCtrl+V", IDM_PASTE 87 | MENUITEM "&Delete\tBksp", IDM_DELETE 88 | MENUITEM SEPARATOR 89 | MENUITEM "Select &All\tCtrl+A", IDM_SELECT_ALL 90 | MENUITEM "Select &Lines\tCtrl+L", IDM_LINE_SELECT 91 | MENUITEM SEPARATOR 92 | MENUITEM "&Find...\tCtrl+F", IDM_FIND 93 | MENUITEM "Find &Next\tF3", IDM_FIND_NEXT 94 | MENUITEM "Find Pre&vious\tShift+F3", IDM_FIND_PREV 95 | MENUITEM "R&eplace...\tCtrl+H", IDM_REPLACE 96 | MENUITEM SEPARATOR 97 | // View 98 | POPUP "&Zoom" { 99 | MENUITEM "Zoom &In\tCtrl+Plus" IDM_ZOOM_IN 100 | MENUITEM "Zoom &Out\tCtrl+Minus" IDM_ZOOM_OUT 101 | MENUITEM "&Restore Default Zoom\tCtrl+0" IDM_ZOOM_RESET 102 | } 103 | MENUITEM "&Word Wrap\tCtrl+Shift+W", IDM_WORD_WRAP 104 | } 105 | } 106 | 107 | STRINGTABLE { 108 | IDS_APP_NAME, "ChromaFiler" 109 | 110 | IDS_UNKNOWN_ERROR, "Unknown error" 111 | IDS_ERROR_CAPTION, "Error" 112 | IDS_CANT_FIND_ITEM, "Unable to find item at path: %1" 113 | IDS_INVALID_CHARS, "File name cannot contain the following characters:\n\\ / : * ? "" < > |" 114 | 115 | IDS_CHROMATEXT, "ChromaText" 116 | 117 | IDS_SETTINGS_CAPTION, "ChromaFiler Settings" 118 | IDS_FONT_NAME, "%1 %2!d!pt" 119 | IDS_NO_UPDATE_CAPTION, "No updates available" 120 | IDS_NO_UPDATE, "You are using the latest version." 121 | IDS_UPDATE_ERROR, "Error checking for updates" 122 | IDS_UPDATE_NOTIF_TITLE, "ChromaFiler update" 123 | IDS_UPDATE_NOTIF_INFO, "A new version of ChromaFiler is available. Click the purple icon below to download." 124 | 125 | IDS_NEWLINES_CRLF, "Windows (CRLF)" 126 | IDS_NEWLINES_LF, "Unix (LF)" 127 | IDS_NEWLINES_CR, "Classic Mac (CR)" 128 | 129 | IDS_ENCODING_UTF8, "UTF-8" 130 | IDS_ENCODING_UTF8BOM, "UTF-8 with BOM" 131 | IDS_ENCODING_UTF16LE, "UTF-16 LE" 132 | IDS_ENCODING_UTF16BE, "UTF-16 BE" 133 | IDS_ENCODING_ANSI, "ANSI" 134 | 135 | IDS_OPEN_PARENT_COMMAND,"Open Parent\n(Alt+Up)" 136 | IDS_MENU_COMMAND, "Menu..." 137 | IDS_REFRESH_COMMAND, "Refresh\n(Ctrl+R)" 138 | IDS_NEW_ITEM_COMMAND, "New..." 139 | IDS_VIEW_COMMAND, "View..." 140 | IDS_SAVE_COMMAND, "Save\n(Ctrl+S)" 141 | IDS_DELETE_COMMAND, "Delete file\n(Ctrl+Shift+D)" 142 | 143 | IDS_FOLDER_STATUS, "%1!d! items" 144 | IDS_FOLDER_STATUS_SEL, "%1!d! items, %2!d! selected" 145 | IDS_FOLDER_ERROR, "Couldn't open folder" 146 | 147 | IDS_TEXT_LOADING, "Reading file..." 148 | IDS_TEXT_STATUS, "Ln %1!d!, Col %2!d!" 149 | IDS_TEXT_STATUS_SEL, "Ln %1!d!, Col %2!d! (%3!d! selected)" 150 | IDS_TEXT_STATUS_REPLACE,"Replaced %1!d! occurrences." 151 | IDS_TEXT_CANT_FIND, "Cannot find text!" 152 | IDS_TEXT_UNDO, "&Undo %1" 153 | IDS_TEXT_REDO, "&Redo %1" 154 | 155 | IDS_TEXT_UNDO_UNKNOWN, "" 156 | IDS_TEXT_UNDO_TYPING, "typing" 157 | IDS_TEXT_UNDO_DELETE, "delete" 158 | IDS_TEXT_UNDO_DRAGDROP, "drag-and-drop" 159 | IDS_TEXT_UNDO_CUT, "cut" 160 | IDS_TEXT_UNDO_PASTE, "paste" 161 | IDS_TEXT_UNDO_AUTOTABLE,"table" 162 | 163 | IDS_SAVE_PROMPT, "Do you want to save changes to %1?" 164 | IDS_DELETE_PROMPT, "Are you sure you want to delete %1?" 165 | IDS_UNSAVED_CAPTION, "Unsaved changes" 166 | IDS_SAVE_BUTTON, "&Save" 167 | IDS_DONT_SAVE_BUTTON, "Do&n't Save" 168 | IDS_DELETE_BUTTON, "&Delete" 169 | IDS_SAVE_ERROR, "Couldn't save" 170 | 171 | IDS_SUCCESS_CAPTION, "Success" 172 | IDS_CONFIRM_CAPTION, "Are you sure?" 173 | IDS_BROWSER_SET_FAILED, "Failed to set default browser" 174 | IDS_BROWSER_SET, "ChromaFiler has been set as your default browser. You can revert this change in ChromaFiler settings." 175 | IDS_BROWSER_RESET, "File Explorer has been restored as your default browser." 176 | IDS_REQUIRE_CONTEXT, "ChromaFiler must be installed with the Folder Context Menu enabled to be able to change the default browser." 177 | IDS_BROWSER_SET_CONFIRM,"This will replace Windows File Explorer as the default program for opening all folders." 178 | 179 | IDS_WELCOME_HEADER, "Welcome" 180 | IDS_WELCOME_BODY, "This appears to be your first time using ChromaFiler." 181 | IDS_WELCOME_TUTORIAL, "&Read Tutorial\nRecommended for first-time users" 182 | IDS_WELCOME_TRAY, "&Enable Tray\nFloating toolbar with quick access to folders" 183 | IDS_WELCOME_BROWSER, "&Make default file browser\nReplace File Explorer as your default browser" 184 | IDS_WELCOME_UPDATE, "Check for &updates weekly" 185 | 186 | IDS_ADMIN_WARNING, "You are attempting to run ChromaFiler as Administrator. This is not recommended!\n\nContinue anyway?" 187 | IDS_DONT_ASK, "Don't ask again" 188 | 189 | IDS_LEGAL_INFO, "\ 190 | Copyright (c) 2023 J. van't Hoog\r\n\ 191 | \r\n\ 192 | ChromaFiler is available under the GNU General Public License v3.0.\r\n\ 193 | A copy of the license can be found in the installation folder.\r\n\ 194 | The source code is available at https://github.com/vanjac/chromafiler\r\n\ 195 | \r\n\ 196 | If you enjoy ChromaFiler, please consider donating to support its development. Click the Donate button above.\r\n\ 197 | \r\n\ 198 | This software uses materials from the following sources:\r\n\ 199 | - Segoe MDL2 Assets font\r\n\ 200 | \tMicrosoft\r\n\ 201 | \thttps://aka.ms/SegoeFonts\r\n\ 202 | - Adwaita Icon Theme\r\n\ 203 | \tGNOME Project\r\n\ 204 | \tLicensed under GNU LGPL v3\r\n\ 205 | \thttps://gitlab.gnome.org/GNOME/adwaita-icon-theme\r\n\ 206 | - Material Design Icons\r\n\ 207 | \tPictogrammers\r\n\ 208 | \tLicensed under Apache License 2.0\r\n\ 209 | \thttps://pictogrammers.com/library/mdi/\r\n\ 210 | " 211 | } 212 | 213 | #include "dialog.rc" 214 | -------------------------------------------------------------------------------- /src/text/resource.rc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "..\resource.h" 3 | 4 | LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT 5 | 6 | 101 ICON "..\\res\\text.ico" 7 | 8 | 1 VERSIONINFO 9 | FILEVERSION CHROMAFILER_VERSION 10 | PRODUCTVERSION CHROMAFILER_VERSION 11 | FILEOS VOS_NT_WINDOWS32 12 | FILETYPE VFT_APP 13 | { 14 | BLOCK "StringFileInfo" { 15 | BLOCK "040904B0" { 16 | VALUE "CompanyName", "chroma zone\0" 17 | VALUE "FileDescription", "ChromaText\0" 18 | VALUE "FileVersion", CHROMAFILER_VERSION_STRING 19 | VALUE "LegalCopyright", "Copyright (c) J. van't Hoog\0" 20 | VALUE "ProductName", "ChromaFiler\0" 21 | VALUE "ProductVersion", CHROMAFILER_VERSION_STRING 22 | } 23 | } 24 | BLOCK "VarFileInfo" { 25 | VALUE "Translation", 0x0409, 0x04B0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/text/textmain.cpp: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include 3 | #include 4 | 5 | int __stdcall wWinMainCRTStartup() { 6 | HANDLE heap = GetProcessHeap(); 7 | 8 | wchar_t exePath[MAX_PATH]; 9 | exePath[0] = 0; 10 | GetModuleFileName(nullptr, exePath, MAX_PATH); 11 | PathRemoveFileSpec(exePath); 12 | PathAppend(exePath, L"ChromaFiler.exe"); 13 | 14 | const wchar_t *cmdLine = GetCommandLine(); 15 | int cmdLineLen = lstrlen(cmdLine); 16 | 17 | const wchar_t APPEND[] = L" /text"; 18 | int size = sizeof(wchar_t) * (cmdLineLen + _countof(APPEND) + 1); 19 | wchar_t *cmdLineFull = (wchar_t *)HeapAlloc(heap, 0, size); 20 | lstrcpy(cmdLineFull, cmdLine); 21 | lstrcpy(cmdLineFull + cmdLineLen, APPEND); 22 | 23 | STARTUPINFO startup; 24 | startup.cb = sizeof(startup); 25 | GetStartupInfo(&startup); 26 | 27 | PROCESS_INFORMATION info; 28 | info.hProcess = nullptr; 29 | info.hThread = nullptr; 30 | checkLE(CreateProcess(exePath, cmdLineFull, nullptr, nullptr, TRUE, 31 | DETACHED_PROCESS, nullptr, nullptr, &startup, &info)); 32 | checkLE(CloseHandle(info.hProcess)); 33 | checkLE(CloseHandle(info.hThread)); 34 | 35 | HeapFree(heap, 0, cmdLineFull); 36 | return 0; 37 | } 38 | --------------------------------------------------------------------------------