├── .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 |
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 |
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 |
--------------------------------------------------------------------------------