4 * @brief Implementation file for Shell Options dialog.
11 #include "OptionsMgr.h"
12 #include "OptionsPanel.h"
13 #include "Constants.h"
14 #include "Environment.h"
16 #include "Win_VersionHelper.h"
22 /// Flags for enabling and mode of extension
25 CONTEXT_F_ENABLED = 0x0a,
26 CONTEXT_F_ADVANCED = 0x0a,
27 CONTEXT_F_COMPARE_AS = 0x04
31 static const tchar_t* f_RegValueEnabled = _T("ContextMenuEnabled");
32 static const tchar_t* f_RegValuePath = _T("Executable");
34 static bool IsShellExtensionRegistered(bool peruser)
38 DWORD ulOptions = KEY_QUERY_VALUE;
40 auto Is64BitWindows = []() { BOOL f64 = FALSE; return IsWow64Process(GetCurrentProcess(), &f64) && f64; };
41 DWORD ulOptions = KEY_QUERY_VALUE | (Is64BitWindows() ? KEY_WOW64_64KEY : 0);
43 if (ERROR_SUCCESS == RegOpenKeyEx(peruser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, _T("Software\\Classes\\CLSID\\{4E716236-AA30-4C65-B225-D68BBA81E9C2}"), 0, ulOptions, &hKey))
51 static bool IsWinMergeContextMenuRegistered()
55 DWORD ulOptions = KEY_QUERY_VALUE;
57 auto Is64BitWindows = []() { BOOL f64 = FALSE; return IsWow64Process(GetCurrentProcess(), &f64) && f64; };
58 DWORD ulOptions = KEY_QUERY_VALUE | (Is64BitWindows() ? KEY_WOW64_64KEY : 0);
60 if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Windows\\CurrentVersion\\AppHost\\IndexedDB\\WinMerge_83g614hpn1ttr"), 0, ulOptions, &hKey))
68 static bool RegisterShellExtension(bool unregister, bool peruser)
70 tchar_t szSystem32[260] = { 0 };
71 tchar_t szSysWow64[260] = { 0 };
72 GetSystemDirectory(szSystem32, sizeof(szSystem32) / sizeof(szSystem32[0]));
73 GetSystemWow64Directory(szSysWow64, sizeof(szSysWow64) / sizeof(szSysWow64[0]));
75 String progpath = env::GetProgPath();
76 String regsvr32 = paths::ConcatPath(szSystem32, _T("regsvr32.exe"));
78 String options = (unregister ? _T("/s /u") : _T("/s"));
79 options += peruser ? _T(" /n /i:user") : _T("");
80 SHELLEXECUTEINFO sei = { sizeof(sei) };
82 sei.lpVerb = _T("runas");
86 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionARM64.dll")) + _T("\"");
88 sei.lpFile = regsvr32.c_str();
89 sei.lpParameters = args.c_str();
90 return !!ShellExecuteEx(&sei);
92 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionX64.dll")) + _T("\"");
94 sei.lpFile = regsvr32.c_str();
95 sei.lpParameters = args.c_str();
98 regsvr32 = paths::ConcatPath(szSysWow64, _T("regsvr32.exe"));
99 args = options + _T("\"") + paths::ConcatPath(progpath, _T("ShellExtensionU.dll")) + _T("\"");
100 sei.lpFile = regsvr32.c_str();
101 sei.lpParameters = args.c_str();
102 return !!ShellExecuteEx(&sei);
108 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionARM.dll")) + _T("\"");
110 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionU.dll")) + _T("\"");
112 sei.lpFile = regsvr32.c_str();
113 sei.lpParameters = args.c_str();
114 return !!ShellExecuteEx(&sei);
118 static bool RegisterWinMergeContextMenu(bool unregister)
121 String progpath = env::GetProgPath();
122 String packagepath = paths::ConcatPath(progpath, _T("WinMergeContextMenuPackage.msix"));
124 cmd = strutils::format(_T("powershell -c \"Add-AppxPackage '%s' -ExternalLocation '%s'\""), packagepath, progpath);
126 cmd = _T("powershell -c \"Get-AppxPackage -name WinMerge | Remove-AppxPackage\"");
128 STARTUPINFO stInfo = { sizeof(STARTUPINFO) };
129 stInfo.dwFlags = STARTF_USESHOWWINDOW;
130 stInfo.wShowWindow = SW_SHOW;
131 PROCESS_INFORMATION processInfo;
132 bool retVal = !!CreateProcess(nullptr, (tchar_t*)cmd.c_str(),
133 nullptr, nullptr, FALSE, CREATE_DEFAULT_ERROR_MODE, nullptr, nullptr,
134 &stInfo, &processInfo);
137 CloseHandle(processInfo.hThread);
138 CloseHandle(processInfo.hProcess);
142 PropShell::PropShell(COptionsMgr *optionsMgr)
143 : OptionsPanel(optionsMgr, PropShell::IDD)
144 , m_bContextAdded(false)
145 , m_bContextAdvanced(false)
146 , m_bContextCompareAs(false)
150 BOOL PropShell::OnInitDialog()
152 OptionsPanel::OnInitDialog();
154 #ifndef BCM_SETSHIELD
155 #define BCM_SETSHIELD (0x1600/*BCM_FIRST*/ + 0x000C)
158 SendDlgItemMessage(IDC_REGISTER_SHELLEXTENSION, BCM_SETSHIELD, 0, TRUE);
159 SendDlgItemMessage(IDC_UNREGISTER_SHELLEXTENSION, BCM_SETSHIELD, 0, TRUE);
161 // Update shell extension checkboxes
163 GetContextRegValues();
164 AdvancedContextMenuCheck();
167 SetTimer(0, 1000, nullptr);
169 return TRUE; // return TRUE unless you set the focus to a control
172 void PropShell::DoDataExchange(CDataExchange* pDX)
174 CPropertyPage::DoDataExchange(pDX);
175 //{{AFX_DATA_MAP(PropShell)
176 DDX_Check(pDX, IDC_EXPLORER_CONTEXT, m_bContextAdded);
177 DDX_Check(pDX, IDC_EXPLORER_ADVANCED, m_bContextAdvanced);
178 DDX_Check(pDX, IDC_EXPLORER_COMPARE_AS, m_bContextCompareAs);
182 BEGIN_MESSAGE_MAP(PropShell, OptionsPanel)
183 //{{AFX_MSG_MAP(PropShell)
184 ON_BN_CLICKED(IDC_EXPLORER_CONTEXT, OnAddToExplorer)
185 ON_BN_CLICKED(IDC_EXPLORER_ADVANCED, OnAddToExplorerAdvanced)
186 ON_BN_CLICKED(IDC_REGISTER_SHELLEXTENSION, OnRegisterShellExtension)
187 ON_BN_CLICKED(IDC_UNREGISTER_SHELLEXTENSION, OnUnregisterShellExtension)
188 ON_BN_CLICKED(IDC_REGISTER_SHELLEXTENSION_PERUSER, OnRegisterShellExtensionPerUser)
189 ON_BN_CLICKED(IDC_UNREGISTER_SHELLEXTENSION_PERUSER, OnUnregisterShellExtensionPerUser)
190 ON_BN_CLICKED(IDC_REGISTER_WINMERGECONTEXTMENU, OnRegisterWinMergeContextMenu)
191 ON_BN_CLICKED(IDC_UNREGISTER_WINMERGECONTEXTMENU, OnUnregisterWinMergeContextMenu)
197 * @brief Reads options values from storage to UI.
199 void PropShell::ReadOptions()
201 GetContextRegValues();
205 * @brief Writes options values from UI to storage.
207 void PropShell::WriteOptions()
209 bool registered = IsShellExtensionRegistered(false);
210 bool registeredPerUser = IsShellExtensionRegistered(true);
211 bool registeredWinMergeContextMenu = IsWinMergeContextMenuRegistered();
212 if (registered || registeredPerUser || registeredWinMergeContextMenu)
213 SaveMergePath(); // saves context menu settings as well
216 /// Get registry values for ShellExtension
217 void PropShell::GetContextRegValues()
221 retVal = reg.OpenNoCreateWithAccess(HKEY_CURRENT_USER, RegDir, KEY_READ);
222 if (retVal != ERROR_SUCCESS)
224 String msg = strutils::format(_T("Failed to open registry key HKCU/%s:\n\t%d : %s"),
225 RegDir, retVal, GetSysError(retVal));
230 // Read bitmask for shell extension settings
231 DWORD dwContextEnabled = reg.ReadDword(f_RegValueEnabled, 0);
233 if (dwContextEnabled & CONTEXT_F_ENABLED)
234 m_bContextAdded = true;
236 if (dwContextEnabled & CONTEXT_F_ADVANCED)
237 m_bContextAdvanced = true;
239 if (dwContextEnabled & CONTEXT_F_COMPARE_AS)
240 m_bContextCompareAs = true;
243 /// Set registry values for ShellExtension
244 void PropShell::OnAddToExplorer()
246 AdvancedContextMenuCheck();
250 void PropShell::OnAddToExplorerAdvanced()
252 CompareAsContextMenuCheck();
256 /// Saves given path to registry for ShellExtension, and Context Menu settings
257 void PropShell::SaveMergePath()
259 tchar_t temp[MAX_PATH] = {0};
261 GetModuleFileName(AfxGetInstanceHandle(), temp, MAX_PATH);
264 retVal = reg.Open(HKEY_CURRENT_USER, RegDir);
265 if (retVal != ERROR_SUCCESS)
267 String msg = strutils::format(_T("Failed to open registry key HKCU/%s:\n\t%d : %s"),
268 RegDir, retVal, GetSysError(retVal));
273 // Save path to WinMerge(U).exe
274 retVal = reg.WriteString(f_RegValuePath, temp);
275 if (retVal != ERROR_SUCCESS)
277 String msg = strutils::format(_T("Failed to set registry value %s:\n\t%d : %s"),
278 f_RegValuePath, retVal, GetSysError(retVal));
282 // Determine bitmask for shell extension
283 DWORD dwContextEnabled = reg.ReadDword(f_RegValueEnabled, 0);
285 dwContextEnabled |= CONTEXT_F_ENABLED;
287 dwContextEnabled &= ~CONTEXT_F_ENABLED;
289 if (m_bContextAdvanced)
290 dwContextEnabled |= CONTEXT_F_ADVANCED;
292 dwContextEnabled &= ~CONTEXT_F_ADVANCED;
294 if (m_bContextCompareAs)
295 dwContextEnabled |= CONTEXT_F_COMPARE_AS;
297 dwContextEnabled &= ~CONTEXT_F_COMPARE_AS;
299 retVal = reg.WriteDword(f_RegValueEnabled, dwContextEnabled);
300 if (retVal != ERROR_SUCCESS)
302 String msg = strutils::format(_T("Failed to set registry value %s to %d:\n\t%d : %s"),
303 f_RegValueEnabled, dwContextEnabled, retVal, GetSysError(retVal));
308 /// Enable/Disable "Advanced menu" checkbox.
309 void PropShell::AdvancedContextMenuCheck()
311 if (!IsDlgButtonChecked(IDC_EXPLORER_CONTEXT))
313 CheckDlgButton(IDC_EXPLORER_ADVANCED, FALSE);
314 m_bContextAdvanced = false;
315 CompareAsContextMenuCheck();
319 void PropShell::CompareAsContextMenuCheck()
321 if (!IsDlgButtonChecked(IDC_EXPLORER_ADVANCED))
323 CheckDlgButton(IDC_EXPLORER_COMPARE_AS, FALSE);
324 m_bContextCompareAs = false;
328 void PropShell::UpdateButtons()
330 bool registered = IsShellExtensionRegistered(false);
331 bool registeredPerUser = IsShellExtensionRegistered(true);
332 bool registerdWinMergeContextMenu = IsWinMergeContextMenuRegistered();
333 bool win11 = IsWin11_OrGreater();
334 EnableDlgItem(IDC_EXPLORER_CONTEXT, registered || registeredPerUser || registerdWinMergeContextMenu);
335 EnableDlgItem(IDC_REGISTER_SHELLEXTENSION, !registered);
336 EnableDlgItem(IDC_UNREGISTER_SHELLEXTENSION, registered);
337 EnableDlgItem(IDC_REGISTER_SHELLEXTENSION_PERUSER, !registeredPerUser);
338 EnableDlgItem(IDC_UNREGISTER_SHELLEXTENSION_PERUSER, registeredPerUser);
339 EnableDlgItem(IDC_REGISTER_WINMERGECONTEXTMENU, !registerdWinMergeContextMenu && win11);
340 EnableDlgItem(IDC_UNREGISTER_WINMERGECONTEXTMENU, registerdWinMergeContextMenu && win11);
341 EnableDlgItem(IDC_EXPLORER_ADVANCED,
342 (registered || registeredPerUser || registerdWinMergeContextMenu) && IsDlgButtonChecked(IDC_EXPLORER_CONTEXT));
343 EnableDlgItem(IDC_EXPLORER_COMPARE_AS,
344 (registered || registeredPerUser || registerdWinMergeContextMenu) && IsDlgButtonChecked(IDC_EXPLORER_ADVANCED));
347 void PropShell::OnRegisterShellExtension()
349 RegisterShellExtension(false, false);
352 void PropShell::OnUnregisterShellExtension()
354 RegisterShellExtension(true, false);
357 void PropShell::OnRegisterShellExtensionPerUser()
359 RegisterShellExtension(false, true);
362 void PropShell::OnUnregisterShellExtensionPerUser()
364 RegisterShellExtension(true, true);
367 void PropShell::OnRegisterWinMergeContextMenu()
369 RegisterWinMergeContextMenu(false);
372 void PropShell::OnUnregisterWinMergeContextMenu()
374 RegisterWinMergeContextMenu(true);
377 void PropShell::OnTimer(UINT_PTR nIDEvent)