OSDN Git Service

Fix a crash problem when the Diff algorithm is set to something other than default...
[winmerge-jp/winmerge-jp.git] / Src / PropShell.cpp
1 /** 
2  * @file  PropShell.cpp
3  *
4  * @brief Implementation file for Shell Options dialog.
5  *
6  */
7
8 #include "stdafx.h"
9 #include "PropShell.h"
10 #include "RegKey.h"
11 #include "OptionsMgr.h"
12 #include "OptionsPanel.h"
13 #include "Constants.h"
14 #include "Environment.h"
15 #include "paths.h"
16 #include "Win_VersionHelper.h"
17 #include "MergeCmdLineInfo.h"
18 #include "JumpList.h"
19 #include "Merge.h"
20
21 #ifdef _DEBUG
22 #define new DEBUG_NEW
23 #endif
24
25 /// Flags for enabling and mode of extension
26 enum
27 {
28         CONTEXT_F_ENABLED = 0x01,
29         CONTEXT_F_ADVANCED = 0x02,
30         CONTEXT_F_COMPARE_AS = 0x04
31 };
32
33 // registry values
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");
37
38 static bool IsShellExtensionRegistered(bool peruser)
39 {
40         HKEY hKey;
41 #ifdef _WIN64
42         DWORD ulOptions = KEY_QUERY_VALUE;
43 #else
44         auto Is64BitWindows = []() { BOOL f64 = FALSE; return IsWow64Process(GetCurrentProcess(), &f64) && f64; };
45         DWORD ulOptions = KEY_QUERY_VALUE | (Is64BitWindows() ? KEY_WOW64_64KEY : 0);
46 #endif
47         if (ERROR_SUCCESS == RegOpenKeyEx(peruser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, _T("Software\\Classes\\CLSID\\{4E716236-AA30-4C65-B225-D68BBA81E9C2}"), 0, ulOptions, &hKey))
48         {
49                 RegCloseKey(hKey);
50                 return true;
51         }
52         return false;
53 }
54
55 static bool IsWinMergeContextMenuRegistered()
56 {
57         HKEY hKey;
58 #ifdef _WIN64
59         DWORD ulOptions = KEY_QUERY_VALUE;
60 #else
61         auto Is64BitWindows = []() { BOOL f64 = FALSE; return IsWow64Process(GetCurrentProcess(), &f64) && f64; };
62         DWORD ulOptions = KEY_QUERY_VALUE | (Is64BitWindows() ? KEY_WOW64_64KEY : 0);
63 #endif
64         if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Windows\\CurrentVersion\\AppHost\\IndexedDB\\WinMerge_83g614hpn1ttr"), 0, ulOptions, &hKey))
65         {
66                 RegCloseKey(hKey);
67                 return true;
68         }
69         return false;
70 }
71
72 static bool RegisterShellExtension(bool unregister, bool peruser)
73 {
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]));
78
79         String progpath = env::GetProgPath();
80         String regsvr32 = paths::ConcatPath(szSystem32, _T("regsvr32.exe"));
81         String args;
82         String options = (unregister ? _T("/s /u") : _T("/s"));
83         options += peruser ? _T(" /n /i:user") : _T("");
84         SHELLEXECUTEINFO sei = { sizeof(sei) };
85         if (!peruser)
86                 sei.lpVerb = _T("runas");
87         if (szSysWow64[0])
88         {
89 #if defined _M_ARM64
90                 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionARM64.dll")) + _T("\"");
91
92                 sei.lpFile = regsvr32.c_str();
93                 sei.lpParameters = args.c_str();
94                 return !!ShellExecuteEx(&sei);
95 #else
96                 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionX64.dll")) + _T("\"");
97
98                 sei.lpFile = regsvr32.c_str();
99                 sei.lpParameters = args.c_str();
100                 ShellExecuteEx(&sei);
101
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);
107 #endif
108         }
109         else
110         {
111 #if defined _M_ARM
112                 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionARM.dll")) + _T("\"");
113 #else
114                 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionU.dll")) + _T("\"");
115 #endif
116                 sei.lpFile = regsvr32.c_str();
117                 sei.lpParameters = args.c_str();
118                 return !!ShellExecuteEx(&sei);
119         }
120 }
121
122 static bool RegisterWinMergeContextMenu(bool unregister)
123 {
124         String cmd;
125         String progpath = env::GetProgPath();
126         String packagepath = paths::ConcatPath(progpath, _T("WinMergeContextMenuPackage.msix"));
127         if (!unregister)
128                 cmd = strutils::format(_T("powershell -c \"Add-AppxPackage '%s' -ExternalLocation '%s'; if (!$?) { pause }\""), packagepath, progpath);
129         else
130                 cmd = _T("powershell -c \"Get-AppxPackage -name WinMerge | Remove-AppxPackage; if (!$?) { pause }\"");
131
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);
139         if (!retVal)
140                 return false;
141         CloseHandle(processInfo.hThread);
142         CloseHandle(processInfo.hProcess);
143         return true;
144 }
145
146 static void LoadListView(CListCtrl& list)
147 {
148         CRect rc;
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));
153         CRegKeyEx reg;
154         reg.Open(HKEY_CURRENT_USER, RegDir);
155         DWORD dwFlags = reg.ReadDword(f_RegValueUserTasksFlags, 0);
156         int i = 0;
157         for (auto [mask, text] : std::vector<std::pair<MergeCmdLineInfo::usertasksflags_t, String>>(
158                 {
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") },
166                 }))
167         {
168                 list.InsertItem(LVIF_TEXT | LVIF_PARAM, i, text.c_str(), 0, 0, 0, mask);
169                 list.SetCheck(i, dwFlags & mask);
170                 ++i;
171         }
172 }
173
174 static void SaveListView(CListCtrl& list)
175 {
176         CRegKeyEx reg;
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))
183         {
184                 reg.WriteDword(f_RegValueUserTasksFlags, dwFlags);
185                 JumpList::AddUserTasks(CMergeApp::CreateUserTasks(dwFlags));
186         }
187 }
188
189 PropShell::PropShell(COptionsMgr *optionsMgr) 
190 : OptionsPanel(optionsMgr, PropShell::IDD)
191 , m_bContextAdded(false)
192 , m_bContextAdvanced(false)
193 , m_bContextCompareAs(false)
194 {
195 }
196
197 BOOL PropShell::OnInitDialog()
198 {
199         OptionsPanel::OnInitDialog();
200
201 #ifndef BCM_SETSHIELD
202 #define BCM_SETSHIELD            (0x1600/*BCM_FIRST*/ + 0x000C)
203 #endif
204
205         SendDlgItemMessage(IDC_REGISTER_SHELLEXTENSION, BCM_SETSHIELD, 0, TRUE);
206         SendDlgItemMessage(IDC_UNREGISTER_SHELLEXTENSION, BCM_SETSHIELD, 0, TRUE);
207
208         LoadListView(m_list);
209
210         // Update shell extension checkboxes
211         UpdateButtons();
212         GetContextRegValues();
213         AdvancedContextMenuCheck();
214         UpdateData(FALSE);
215
216         SetTimer(0, 1000, nullptr);
217
218         return TRUE;  // return TRUE  unless you set the focus to a control
219 }
220
221 void PropShell::DoDataExchange(CDataExchange* pDX)
222 {
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);
229         //}}AFX_DATA_MAP
230 }
231
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)
243         ON_WM_TIMER()
244         //}}AFX_MSG_MAP
245 END_MESSAGE_MAP()
246
247 /** 
248  * @brief Reads options values from storage to UI.
249  */
250 void PropShell::ReadOptions()
251 {
252         GetContextRegValues();
253 }
254
255 /** 
256  * @brief Writes options values from UI to storage.
257  */
258 void PropShell::WriteOptions()
259 {
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
265         if (m_list.m_hWnd)
266                 SaveListView(m_list);
267 }
268
269 /// Get registry values for ShellExtension
270 void PropShell::GetContextRegValues()
271 {
272         CRegKeyEx reg;
273         LONG retVal = 0;
274         retVal = reg.OpenNoCreateWithAccess(HKEY_CURRENT_USER, RegDir, KEY_READ);
275         if (retVal != ERROR_SUCCESS)
276         {
277                 String msg = strutils::format(_T("Failed to open registry key HKCU/%s:\n\t%d : %s"),
278                         RegDir, retVal, GetSysError(retVal));
279                 LogErrorString(msg);
280                 return;
281         }
282
283         // Read bitmask for shell extension settings
284         DWORD dwContextEnabled = reg.ReadDword(f_RegValueEnabled, 0);
285
286         if (dwContextEnabled & CONTEXT_F_ENABLED)
287                 m_bContextAdded = true;
288
289         if (dwContextEnabled & CONTEXT_F_ADVANCED)
290                 m_bContextAdvanced = true;
291
292         if (dwContextEnabled & CONTEXT_F_COMPARE_AS)
293                 m_bContextCompareAs = true;
294 }
295
296 /// Set registry values for ShellExtension
297 void PropShell::OnAddToExplorer()
298 {
299         AdvancedContextMenuCheck();
300         UpdateButtons();
301 }
302
303 void PropShell::OnAddToExplorerAdvanced()
304 {
305         CompareAsContextMenuCheck();
306         UpdateButtons();
307 }
308
309 /// Saves given path to registry for ShellExtension, and Context Menu settings
310 void PropShell::SaveMergePath()
311 {
312         tchar_t temp[MAX_PATH] = {0};
313         LONG retVal = 0;
314         GetModuleFileName(AfxGetInstanceHandle(), temp, MAX_PATH);
315
316         CRegKeyEx reg;
317         retVal = reg.Open(HKEY_CURRENT_USER, RegDir);
318         if (retVal != ERROR_SUCCESS)
319         {
320                 String msg = strutils::format(_T("Failed to open registry key HKCU/%s:\n\t%d : %s"),
321                         RegDir, retVal, GetSysError(retVal));
322                 LogErrorString(msg);
323                 return;
324         }
325
326         // Save path to WinMerge(U).exe
327         retVal = reg.WriteString(f_RegValuePath, temp);
328         if (retVal != ERROR_SUCCESS)
329         {
330                 String msg = strutils::format(_T("Failed to set registry value %s:\n\t%d : %s"),
331                         f_RegValuePath, retVal, GetSysError(retVal));
332                 LogErrorString(msg);
333         }
334
335         // Determine bitmask for shell extension
336         DWORD dwContextEnabled = reg.ReadDword(f_RegValueEnabled, 0);
337         if (m_bContextAdded)
338                 dwContextEnabled |= CONTEXT_F_ENABLED;
339         else
340                 dwContextEnabled &= ~CONTEXT_F_ENABLED;
341
342         if (m_bContextAdvanced)
343                 dwContextEnabled |= CONTEXT_F_ADVANCED;
344         else
345                 dwContextEnabled &= ~CONTEXT_F_ADVANCED;
346
347         if (m_bContextCompareAs)
348                 dwContextEnabled |= CONTEXT_F_COMPARE_AS;
349         else
350                 dwContextEnabled &= ~CONTEXT_F_COMPARE_AS;
351
352         retVal = reg.WriteDword(f_RegValueEnabled, dwContextEnabled);
353         if (retVal != ERROR_SUCCESS)
354         {
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));
357                 LogErrorString(msg);
358         }
359 }
360
361 /// Enable/Disable "Advanced menu" checkbox.
362 void PropShell::AdvancedContextMenuCheck()
363 {
364         if (!IsDlgButtonChecked(IDC_EXPLORER_CONTEXT))
365         {
366                 CheckDlgButton(IDC_EXPLORER_ADVANCED, FALSE);
367                 m_bContextAdvanced = false;
368                 CompareAsContextMenuCheck();
369         }
370 }
371
372 void PropShell::CompareAsContextMenuCheck()
373 {
374         if (!IsDlgButtonChecked(IDC_EXPLORER_ADVANCED))
375         {
376                 CheckDlgButton(IDC_EXPLORER_COMPARE_AS, FALSE);
377                 m_bContextCompareAs = false;
378         }
379 }
380
381 void PropShell::UpdateButtons()
382 {
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);
401 }
402
403 void PropShell::OnRegisterShellExtension()
404 {
405         RegisterShellExtension(false, false);
406 }
407
408 void PropShell::OnUnregisterShellExtension()
409 {
410         RegisterShellExtension(true, false);
411 }
412
413 void PropShell::OnRegisterShellExtensionPerUser()
414 {
415         RegisterShellExtension(false, true);
416 }
417
418 void PropShell::OnUnregisterShellExtensionPerUser()
419 {
420         RegisterShellExtension(true, true);
421 }
422
423 void PropShell::OnRegisterWinMergeContextMenu()
424 {
425         RegisterWinMergeContextMenu(false);
426 }
427
428 void PropShell::OnUnregisterWinMergeContextMenu()
429 {
430         RegisterWinMergeContextMenu(true);
431 }
432
433 void PropShell::OnClearAllRecentItems()
434 {
435         JumpList::RemoveRecentDocs();
436 }
437
438 void PropShell::OnTimer(UINT_PTR nIDEvent)
439 {
440         UpdateButtons();
441 }