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"
17 #include "MergeCmdLineInfo.h"
25 /// Flags for enabling and mode of extension
28 CONTEXT_F_ENABLED = 0x01,
29 CONTEXT_F_ADVANCED = 0x02,
30 CONTEXT_F_COMPARE_AS = 0x04
34 static const tchar_t* f_RegValueEnabled = _T("ContextMenuEnabled");
35 static const tchar_t* f_RegValuePath = _T("Executable");
36 static const tchar_t* f_RegValueUserTasksFlags = _T("UserTasksFlags");
38 static bool IsShellExtensionRegistered(bool peruser)
42 DWORD ulOptions = KEY_QUERY_VALUE;
44 auto Is64BitWindows = []() { BOOL f64 = FALSE; return IsWow64Process(GetCurrentProcess(), &f64) && f64; };
45 DWORD ulOptions = KEY_QUERY_VALUE | (Is64BitWindows() ? KEY_WOW64_64KEY : 0);
47 if (ERROR_SUCCESS == RegOpenKeyEx(peruser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, _T("Software\\Classes\\CLSID\\{4E716236-AA30-4C65-B225-D68BBA81E9C2}"), 0, ulOptions, &hKey))
55 static bool IsWinMergeContextMenuRegistered()
59 DWORD ulOptions = KEY_QUERY_VALUE;
61 auto Is64BitWindows = []() { BOOL f64 = FALSE; return IsWow64Process(GetCurrentProcess(), &f64) && f64; };
62 DWORD ulOptions = KEY_QUERY_VALUE | (Is64BitWindows() ? KEY_WOW64_64KEY : 0);
64 if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Windows\\CurrentVersion\\AppHost\\IndexedDB\\WinMerge_83g614hpn1ttr"), 0, ulOptions, &hKey))
72 static bool RegisterShellExtension(bool unregister, bool peruser)
74 tchar_t szSystem32[260] = { 0 };
75 tchar_t szSysWow64[260] = { 0 };
76 GetSystemDirectory(szSystem32, sizeof(szSystem32) / sizeof(szSystem32[0]));
77 GetSystemWow64Directory(szSysWow64, sizeof(szSysWow64) / sizeof(szSysWow64[0]));
79 String progpath = env::GetProgPath();
80 String regsvr32 = paths::ConcatPath(szSystem32, _T("regsvr32.exe"));
82 String options = (unregister ? _T("/s /u") : _T("/s"));
83 options += peruser ? _T(" /n /i:user") : _T("");
84 SHELLEXECUTEINFO sei = { sizeof(sei) };
86 sei.lpVerb = _T("runas");
90 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionARM64.dll")) + _T("\"");
92 sei.lpFile = regsvr32.c_str();
93 sei.lpParameters = args.c_str();
94 return !!ShellExecuteEx(&sei);
96 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionX64.dll")) + _T("\"");
98 sei.lpFile = regsvr32.c_str();
99 sei.lpParameters = args.c_str();
100 ShellExecuteEx(&sei);
102 regsvr32 = paths::ConcatPath(szSysWow64, _T("regsvr32.exe"));
103 args = options + _T("\"") + paths::ConcatPath(progpath, _T("ShellExtensionU.dll")) + _T("\"");
104 sei.lpFile = regsvr32.c_str();
105 sei.lpParameters = args.c_str();
106 return !!ShellExecuteEx(&sei);
112 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionARM.dll")) + _T("\"");
114 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionU.dll")) + _T("\"");
116 sei.lpFile = regsvr32.c_str();
117 sei.lpParameters = args.c_str();
118 return !!ShellExecuteEx(&sei);
122 static bool RegisterWinMergeContextMenu(bool unregister)
125 String progpath = env::GetProgPath();
126 String packagepath = paths::ConcatPath(progpath, _T("WinMergeContextMenuPackage.msix"));
128 cmd = strutils::format(_T("powershell -c \"Add-AppxPackage '%s' -ExternalLocation '%s'; if (!$?) { pause }\""), packagepath, progpath);
130 cmd = _T("powershell -c \"Get-AppxPackage -name WinMerge | Remove-AppxPackage; if (!$?) { pause }\"");
132 STARTUPINFO stInfo = { sizeof(STARTUPINFO) };
133 stInfo.dwFlags = STARTF_USESHOWWINDOW;
134 stInfo.wShowWindow = SW_SHOW;
135 PROCESS_INFORMATION processInfo;
136 bool retVal = !!CreateProcess(nullptr, (tchar_t*)cmd.c_str(),
137 nullptr, nullptr, FALSE, CREATE_DEFAULT_ERROR_MODE, nullptr, nullptr,
138 &stInfo, &processInfo);
141 CloseHandle(processInfo.hThread);
142 CloseHandle(processInfo.hProcess);
146 static void LoadListView(CListCtrl& list)
149 list.DeleteAllItems();
150 list.SetExtendedStyle(LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT);
151 list.GetClientRect(&rc);
152 list.InsertColumn(0, _T(""), LVCFMT_LEFT, rc.Width() - GetSystemMetrics(SM_CXVSCROLL));
154 reg.Open(HKEY_CURRENT_USER, RegDir);
155 DWORD dwFlags = reg.ReadDword(f_RegValueUserTasksFlags, 0);
157 for (auto [mask, text] : std::vector<std::pair<MergeCmdLineInfo::usertasksflags_t, String>>(
159 { MergeCmdLineInfo::NEW_TEXT_COMPARE, _("New Text Compare") },
160 { MergeCmdLineInfo::NEW_TABLE_COMPARE, _("New Table Compare") },
161 { MergeCmdLineInfo::NEW_BINARY_COMPARE, _("New Binary Compare") },
162 { MergeCmdLineInfo::NEW_IMAGE_COMPARE, _("New Image Compare") },
163 { MergeCmdLineInfo::NEW_WEBPAGE_COMPARE, _("New Webpage Compare") },
164 { MergeCmdLineInfo::CLIPBOARD_COMPARE, _("Clipboard Compare") },
165 { MergeCmdLineInfo::SHOW_OPTIONS_DIALOG, _("Options") },
168 list.InsertItem(LVIF_TEXT | LVIF_PARAM, i, text.c_str(), 0, 0, 0, mask);
169 list.SetCheck(i, dwFlags & mask);
174 static void SaveListView(CListCtrl& list)
177 MergeCmdLineInfo::usertasksflags_t dwFlags = 0;
178 reg.Open(HKEY_CURRENT_USER, RegDir);
179 int itemCount = list.GetItemCount();
180 for (int i = 0; i < itemCount; ++i)
181 dwFlags |= list.GetCheck(i) ? list.GetItemData(i) : 0;
182 if (dwFlags != reg.ReadDword(f_RegValueUserTasksFlags, 0))
184 reg.WriteDword(f_RegValueUserTasksFlags, dwFlags);
185 JumpList::AddUserTasks(CMergeApp::CreateUserTasks(dwFlags));
189 PropShell::PropShell(COptionsMgr *optionsMgr)
190 : OptionsPanel(optionsMgr, PropShell::IDD)
191 , m_bContextAdded(false)
192 , m_bContextAdvanced(false)
193 , m_bContextCompareAs(false)
197 BOOL PropShell::OnInitDialog()
199 OptionsPanel::OnInitDialog();
201 #ifndef BCM_SETSHIELD
202 #define BCM_SETSHIELD (0x1600/*BCM_FIRST*/ + 0x000C)
205 SendDlgItemMessage(IDC_REGISTER_SHELLEXTENSION, BCM_SETSHIELD, 0, TRUE);
206 SendDlgItemMessage(IDC_UNREGISTER_SHELLEXTENSION, BCM_SETSHIELD, 0, TRUE);
208 LoadListView(m_list);
210 // Update shell extension checkboxes
212 GetContextRegValues();
213 AdvancedContextMenuCheck();
216 SetTimer(0, 1000, nullptr);
218 return TRUE; // return TRUE unless you set the focus to a control
221 void PropShell::DoDataExchange(CDataExchange* pDX)
223 CPropertyPage::DoDataExchange(pDX);
224 //{{AFX_DATA_MAP(PropShell)
225 DDX_Check(pDX, IDC_EXPLORER_CONTEXT, m_bContextAdded);
226 DDX_Check(pDX, IDC_EXPLORER_ADVANCED, m_bContextAdvanced);
227 DDX_Check(pDX, IDC_EXPLORER_COMPARE_AS, m_bContextCompareAs);
228 DDX_Control(pDX, IDC_JUMP_LIST, m_list);
232 BEGIN_MESSAGE_MAP(PropShell, OptionsPanel)
233 //{{AFX_MSG_MAP(PropShell)
234 ON_BN_CLICKED(IDC_EXPLORER_CONTEXT, OnAddToExplorer)
235 ON_BN_CLICKED(IDC_EXPLORER_ADVANCED, OnAddToExplorerAdvanced)
236 ON_BN_CLICKED(IDC_REGISTER_SHELLEXTENSION, OnRegisterShellExtension)
237 ON_BN_CLICKED(IDC_UNREGISTER_SHELLEXTENSION, OnUnregisterShellExtension)
238 ON_BN_CLICKED(IDC_REGISTER_SHELLEXTENSION_PERUSER, OnRegisterShellExtensionPerUser)
239 ON_BN_CLICKED(IDC_UNREGISTER_SHELLEXTENSION_PERUSER, OnUnregisterShellExtensionPerUser)
240 ON_BN_CLICKED(IDC_REGISTER_WINMERGECONTEXTMENU, OnRegisterWinMergeContextMenu)
241 ON_BN_CLICKED(IDC_UNREGISTER_WINMERGECONTEXTMENU, OnUnregisterWinMergeContextMenu)
242 ON_BN_CLICKED(IDC_CLEAR_ALL_RECENT_ITEMS, OnClearAllRecentItems)
248 * @brief Reads options values from storage to UI.
250 void PropShell::ReadOptions()
252 GetContextRegValues();
256 * @brief Writes options values from UI to storage.
258 void PropShell::WriteOptions()
260 bool registered = IsShellExtensionRegistered(false);
261 bool registeredPerUser = IsShellExtensionRegistered(true);
262 bool registeredWinMergeContextMenu = IsWinMergeContextMenuRegistered();
263 if (registered || registeredPerUser || registeredWinMergeContextMenu)
264 SaveMergePath(); // saves context menu settings as well
266 SaveListView(m_list);
269 /// Get registry values for ShellExtension
270 void PropShell::GetContextRegValues()
274 retVal = reg.OpenNoCreateWithAccess(HKEY_CURRENT_USER, RegDir, KEY_READ);
275 if (retVal != ERROR_SUCCESS)
277 String msg = strutils::format(_T("Failed to open registry key HKCU/%s:\n\t%d : %s"),
278 RegDir, retVal, GetSysError(retVal));
283 // Read bitmask for shell extension settings
284 DWORD dwContextEnabled = reg.ReadDword(f_RegValueEnabled, 0);
286 if (dwContextEnabled & CONTEXT_F_ENABLED)
287 m_bContextAdded = true;
289 if (dwContextEnabled & CONTEXT_F_ADVANCED)
290 m_bContextAdvanced = true;
292 if (dwContextEnabled & CONTEXT_F_COMPARE_AS)
293 m_bContextCompareAs = true;
296 /// Set registry values for ShellExtension
297 void PropShell::OnAddToExplorer()
299 AdvancedContextMenuCheck();
303 void PropShell::OnAddToExplorerAdvanced()
305 CompareAsContextMenuCheck();
309 /// Saves given path to registry for ShellExtension, and Context Menu settings
310 void PropShell::SaveMergePath()
312 tchar_t temp[MAX_PATH] = {0};
314 GetModuleFileName(AfxGetInstanceHandle(), temp, MAX_PATH);
317 retVal = reg.Open(HKEY_CURRENT_USER, RegDir);
318 if (retVal != ERROR_SUCCESS)
320 String msg = strutils::format(_T("Failed to open registry key HKCU/%s:\n\t%d : %s"),
321 RegDir, retVal, GetSysError(retVal));
326 // Save path to WinMerge(U).exe
327 retVal = reg.WriteString(f_RegValuePath, temp);
328 if (retVal != ERROR_SUCCESS)
330 String msg = strutils::format(_T("Failed to set registry value %s:\n\t%d : %s"),
331 f_RegValuePath, retVal, GetSysError(retVal));
335 // Determine bitmask for shell extension
336 DWORD dwContextEnabled = reg.ReadDword(f_RegValueEnabled, 0);
338 dwContextEnabled |= CONTEXT_F_ENABLED;
340 dwContextEnabled &= ~CONTEXT_F_ENABLED;
342 if (m_bContextAdvanced)
343 dwContextEnabled |= CONTEXT_F_ADVANCED;
345 dwContextEnabled &= ~CONTEXT_F_ADVANCED;
347 if (m_bContextCompareAs)
348 dwContextEnabled |= CONTEXT_F_COMPARE_AS;
350 dwContextEnabled &= ~CONTEXT_F_COMPARE_AS;
352 retVal = reg.WriteDword(f_RegValueEnabled, dwContextEnabled);
353 if (retVal != ERROR_SUCCESS)
355 String msg = strutils::format(_T("Failed to set registry value %s to %d:\n\t%d : %s"),
356 f_RegValueEnabled, dwContextEnabled, retVal, GetSysError(retVal));
361 /// Enable/Disable "Advanced menu" checkbox.
362 void PropShell::AdvancedContextMenuCheck()
364 if (!IsDlgButtonChecked(IDC_EXPLORER_CONTEXT))
366 CheckDlgButton(IDC_EXPLORER_ADVANCED, FALSE);
367 m_bContextAdvanced = false;
368 CompareAsContextMenuCheck();
372 void PropShell::CompareAsContextMenuCheck()
374 if (!IsDlgButtonChecked(IDC_EXPLORER_ADVANCED))
376 CheckDlgButton(IDC_EXPLORER_COMPARE_AS, FALSE);
377 m_bContextCompareAs = false;
381 void PropShell::UpdateButtons()
383 bool registered = IsShellExtensionRegistered(false);
384 bool registeredPerUser = IsShellExtensionRegistered(true);
385 bool registerdWinMergeContextMenu = IsWinMergeContextMenuRegistered();
386 bool win11 = IsWin11_OrGreater();
387 bool win7 = IsWin7_OrGreater();
388 EnableDlgItem(IDC_EXPLORER_CONTEXT, registered || registeredPerUser || registerdWinMergeContextMenu);
389 EnableDlgItem(IDC_REGISTER_SHELLEXTENSION, !registered);
390 EnableDlgItem(IDC_UNREGISTER_SHELLEXTENSION, registered);
391 EnableDlgItem(IDC_REGISTER_SHELLEXTENSION_PERUSER, !registeredPerUser);
392 EnableDlgItem(IDC_UNREGISTER_SHELLEXTENSION_PERUSER, registeredPerUser);
393 EnableDlgItem(IDC_REGISTER_WINMERGECONTEXTMENU, !registerdWinMergeContextMenu && win11);
394 EnableDlgItem(IDC_UNREGISTER_WINMERGECONTEXTMENU, registerdWinMergeContextMenu && win11);
395 EnableDlgItem(IDC_EXPLORER_ADVANCED,
396 (registered || registeredPerUser || registerdWinMergeContextMenu) && IsDlgButtonChecked(IDC_EXPLORER_CONTEXT));
397 EnableDlgItem(IDC_EXPLORER_COMPARE_AS,
398 (registered || registeredPerUser || registerdWinMergeContextMenu) && IsDlgButtonChecked(IDC_EXPLORER_ADVANCED));
399 EnableDlgItem(IDC_JUMP_LIST, win7);
400 EnableDlgItem(IDC_CLEAR_ALL_RECENT_ITEMS, win7);
403 void PropShell::OnRegisterShellExtension()
405 RegisterShellExtension(false, false);
408 void PropShell::OnUnregisterShellExtension()
410 RegisterShellExtension(true, false);
413 void PropShell::OnRegisterShellExtensionPerUser()
415 RegisterShellExtension(false, true);
418 void PropShell::OnUnregisterShellExtensionPerUser()
420 RegisterShellExtension(true, true);
423 void PropShell::OnRegisterWinMergeContextMenu()
425 RegisterWinMergeContextMenu(false);
428 void PropShell::OnUnregisterWinMergeContextMenu()
430 RegisterWinMergeContextMenu(true);
433 void PropShell::OnClearAllRecentItems()
435 JumpList::RemoveRecentDocs();
438 void PropShell::OnTimer(UINT_PTR nIDEvent)