OSDN Git Service

refactor
[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
18 #ifdef _DEBUG
19 #define new DEBUG_NEW
20 #endif
21
22 /// Flags for enabling and mode of extension
23 enum
24 {
25         CONTEXT_F_ENABLED = 0x0a,
26         CONTEXT_F_ADVANCED = 0x0a,
27         CONTEXT_F_COMPARE_AS = 0x04
28 };
29
30 // registry values
31 static const tchar_t* f_RegValueEnabled = _T("ContextMenuEnabled");
32 static const tchar_t* f_RegValuePath = _T("Executable");
33
34 static bool IsShellExtensionRegistered(bool peruser)
35 {
36         HKEY hKey;
37 #ifdef _WIN64
38         DWORD ulOptions = KEY_QUERY_VALUE;
39 #else
40         auto Is64BitWindows = []() { BOOL f64 = FALSE; return IsWow64Process(GetCurrentProcess(), &f64) && f64; };
41         DWORD ulOptions = KEY_QUERY_VALUE | (Is64BitWindows() ? KEY_WOW64_64KEY : 0);
42 #endif
43         if (ERROR_SUCCESS == RegOpenKeyEx(peruser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, _T("Software\\Classes\\CLSID\\{4E716236-AA30-4C65-B225-D68BBA81E9C2}"), 0, ulOptions, &hKey))
44         {
45                 RegCloseKey(hKey);
46                 return true;
47         }
48         return false;
49 }
50
51 static bool IsWinMergeContextMenuRegistered()
52 {
53         HKEY hKey;
54 #ifdef _WIN64
55         DWORD ulOptions = KEY_QUERY_VALUE;
56 #else
57         auto Is64BitWindows = []() { BOOL f64 = FALSE; return IsWow64Process(GetCurrentProcess(), &f64) && f64; };
58         DWORD ulOptions = KEY_QUERY_VALUE | (Is64BitWindows() ? KEY_WOW64_64KEY : 0);
59 #endif
60         if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Windows\\CurrentVersion\\AppHost\\IndexedDB\\WinMerge_83g614hpn1ttr"), 0, ulOptions, &hKey))
61         {
62                 RegCloseKey(hKey);
63                 return true;
64         }
65         return false;
66 }
67
68 static bool RegisterShellExtension(bool unregister, bool peruser)
69 {
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]));
74
75         String progpath = env::GetProgPath();
76         String regsvr32 = paths::ConcatPath(szSystem32, _T("regsvr32.exe"));
77         String args;
78         String options = (unregister ? _T("/s /u") : _T("/s"));
79         options += peruser ? _T(" /n /i:user") : _T("");
80         SHELLEXECUTEINFO sei = { sizeof(sei) };
81         if (!peruser)
82                 sei.lpVerb = _T("runas");
83         if (szSysWow64[0])
84         {
85 #if defined _M_ARM64
86                 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionARM64.dll")) + _T("\"");
87
88                 sei.lpFile = regsvr32.c_str();
89                 sei.lpParameters = args.c_str();
90                 return !!ShellExecuteEx(&sei);
91 #else
92                 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionX64.dll")) + _T("\"");
93
94                 sei.lpFile = regsvr32.c_str();
95                 sei.lpParameters = args.c_str();
96                 ShellExecuteEx(&sei);
97
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);
103 #endif
104         }
105         else
106         {
107 #if defined _M_ARM
108                 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionARM.dll")) + _T("\"");
109 #else
110                 args = options + _T(" \"") + paths::ConcatPath(progpath, _T("ShellExtensionU.dll")) + _T("\"");
111 #endif
112                 sei.lpFile = regsvr32.c_str();
113                 sei.lpParameters = args.c_str();
114                 return !!ShellExecuteEx(&sei);
115         }
116 }
117
118 static bool RegisterWinMergeContextMenu(bool unregister)
119 {
120         String cmd;
121         String progpath = env::GetProgPath();
122         String packagepath = paths::ConcatPath(progpath, _T("WinMergeContextMenuPackage.msix"));
123         if (!unregister)
124                 cmd = strutils::format(_T("powershell -c \"Add-AppxPackage '%s' -ExternalLocation '%s'\""), packagepath, progpath);
125         else
126                 cmd = _T("powershell -c \"Get-AppxPackage -name WinMerge | Remove-AppxPackage\"");
127
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);
135         if (!retVal)
136                 return false;
137         CloseHandle(processInfo.hThread);
138         CloseHandle(processInfo.hProcess);
139         return true;
140 }
141
142 PropShell::PropShell(COptionsMgr *optionsMgr) 
143 : OptionsPanel(optionsMgr, PropShell::IDD)
144 , m_bContextAdded(false)
145 , m_bContextAdvanced(false)
146 , m_bContextCompareAs(false)
147 {
148 }
149
150 BOOL PropShell::OnInitDialog()
151 {
152         OptionsPanel::OnInitDialog();
153
154 #ifndef BCM_SETSHIELD
155 #define BCM_SETSHIELD            (0x1600/*BCM_FIRST*/ + 0x000C)
156 #endif
157
158         SendDlgItemMessage(IDC_REGISTER_SHELLEXTENSION, BCM_SETSHIELD, 0, TRUE);
159         SendDlgItemMessage(IDC_UNREGISTER_SHELLEXTENSION, BCM_SETSHIELD, 0, TRUE);
160
161         // Update shell extension checkboxes
162         UpdateButtons();
163         GetContextRegValues();
164         AdvancedContextMenuCheck();
165         UpdateData(FALSE);
166
167         SetTimer(0, 1000, nullptr);
168
169         return TRUE;  // return TRUE  unless you set the focus to a control
170 }
171
172 void PropShell::DoDataExchange(CDataExchange* pDX)
173 {
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);
179         //}}AFX_DATA_MAP
180 }
181
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)
192         ON_WM_TIMER()
193         //}}AFX_MSG_MAP
194 END_MESSAGE_MAP()
195
196 /** 
197  * @brief Reads options values from storage to UI.
198  */
199 void PropShell::ReadOptions()
200 {
201         GetContextRegValues();
202 }
203
204 /** 
205  * @brief Writes options values from UI to storage.
206  */
207 void PropShell::WriteOptions()
208 {
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
214 }
215
216 /// Get registry values for ShellExtension
217 void PropShell::GetContextRegValues()
218 {
219         CRegKeyEx reg;
220         LONG retVal = 0;
221         retVal = reg.OpenNoCreateWithAccess(HKEY_CURRENT_USER, RegDir, KEY_READ);
222         if (retVal != ERROR_SUCCESS)
223         {
224                 String msg = strutils::format(_T("Failed to open registry key HKCU/%s:\n\t%d : %s"),
225                         RegDir, retVal, GetSysError(retVal));
226                 LogErrorString(msg);
227                 return;
228         }
229
230         // Read bitmask for shell extension settings
231         DWORD dwContextEnabled = reg.ReadDword(f_RegValueEnabled, 0);
232
233         if (dwContextEnabled & CONTEXT_F_ENABLED)
234                 m_bContextAdded = true;
235
236         if (dwContextEnabled & CONTEXT_F_ADVANCED)
237                 m_bContextAdvanced = true;
238
239         if (dwContextEnabled & CONTEXT_F_COMPARE_AS)
240                 m_bContextCompareAs = true;
241 }
242
243 /// Set registry values for ShellExtension
244 void PropShell::OnAddToExplorer()
245 {
246         AdvancedContextMenuCheck();
247         UpdateButtons();
248 }
249
250 void PropShell::OnAddToExplorerAdvanced()
251 {
252         CompareAsContextMenuCheck();
253         UpdateButtons();
254 }
255
256 /// Saves given path to registry for ShellExtension, and Context Menu settings
257 void PropShell::SaveMergePath()
258 {
259         tchar_t temp[MAX_PATH] = {0};
260         LONG retVal = 0;
261         GetModuleFileName(AfxGetInstanceHandle(), temp, MAX_PATH);
262
263         CRegKeyEx reg;
264         retVal = reg.Open(HKEY_CURRENT_USER, RegDir);
265         if (retVal != ERROR_SUCCESS)
266         {
267                 String msg = strutils::format(_T("Failed to open registry key HKCU/%s:\n\t%d : %s"),
268                         RegDir, retVal, GetSysError(retVal));
269                 LogErrorString(msg);
270                 return;
271         }
272
273         // Save path to WinMerge(U).exe
274         retVal = reg.WriteString(f_RegValuePath, temp);
275         if (retVal != ERROR_SUCCESS)
276         {
277                 String msg = strutils::format(_T("Failed to set registry value %s:\n\t%d : %s"),
278                         f_RegValuePath, retVal, GetSysError(retVal));
279                 LogErrorString(msg);
280         }
281
282         // Determine bitmask for shell extension
283         DWORD dwContextEnabled = reg.ReadDword(f_RegValueEnabled, 0);
284         if (m_bContextAdded)
285                 dwContextEnabled |= CONTEXT_F_ENABLED;
286         else
287                 dwContextEnabled &= ~CONTEXT_F_ENABLED;
288
289         if (m_bContextAdvanced)
290                 dwContextEnabled |= CONTEXT_F_ADVANCED;
291         else
292                 dwContextEnabled &= ~CONTEXT_F_ADVANCED;
293
294         if (m_bContextCompareAs)
295                 dwContextEnabled |= CONTEXT_F_COMPARE_AS;
296         else
297                 dwContextEnabled &= ~CONTEXT_F_COMPARE_AS;
298
299         retVal = reg.WriteDword(f_RegValueEnabled, dwContextEnabled);
300         if (retVal != ERROR_SUCCESS)
301         {
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));
304                 LogErrorString(msg);
305         }
306 }
307
308 /// Enable/Disable "Advanced menu" checkbox.
309 void PropShell::AdvancedContextMenuCheck()
310 {
311         if (!IsDlgButtonChecked(IDC_EXPLORER_CONTEXT))
312         {
313                 CheckDlgButton(IDC_EXPLORER_ADVANCED, FALSE);
314                 m_bContextAdvanced = false;
315                 CompareAsContextMenuCheck();
316         }
317 }
318
319 void PropShell::CompareAsContextMenuCheck()
320 {
321         if (!IsDlgButtonChecked(IDC_EXPLORER_ADVANCED))
322         {
323                 CheckDlgButton(IDC_EXPLORER_COMPARE_AS, FALSE);
324                 m_bContextCompareAs = false;
325         }
326 }
327
328 void PropShell::UpdateButtons()
329 {
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));
345 }
346
347 void PropShell::OnRegisterShellExtension()
348 {
349         RegisterShellExtension(false, false);
350 }
351
352 void PropShell::OnUnregisterShellExtension()
353 {
354         RegisterShellExtension(true, false);
355 }
356
357 void PropShell::OnRegisterShellExtensionPerUser()
358 {
359         RegisterShellExtension(false, true);
360 }
361
362 void PropShell::OnUnregisterShellExtensionPerUser()
363 {
364         RegisterShellExtension(true, true);
365 }
366
367 void PropShell::OnRegisterWinMergeContextMenu()
368 {
369         RegisterWinMergeContextMenu(false);
370 }
371
372 void PropShell::OnUnregisterWinMergeContextMenu()
373 {
374         RegisterWinMergeContextMenu(true);
375 }
376
377 void PropShell::OnTimer(UINT_PTR nIDEvent)
378 {
379         UpdateButtons();
380 }