Prepare release and bump version numbers to 2.17.0.2
[TortoiseGit.git] / src / SshAskPass / SshAskPass.cpp
blobb5d3e8ec3201b672bed74dc57c4c4bdc29a92ae7
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2016, 2018-2020, 2023 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // SshAskPass.cpp : Defines the entry point for the application.
23 #include "stdafx.h"
24 #include "resource.h"
25 #include <propsys.h>
26 #include <PropKey.h>
27 #include "UnicodeUtils.h"
28 #include "SmartHandle.h"
29 #include <memory>
30 #include "DarkModeHelper.h"
31 #include "registry.h"
32 #include "DPIAware.h"
33 #include <afxtaskdialog.h>
35 #include <commctrl.h>
36 #pragma comment(lib, "comctl32.lib")
37 #pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
39 #define MAX_LOADSTRING 256
41 // Global Variables:
42 HINSTANCE hInst; // current instance
43 bool g_darkmode = false;
44 int m_dpi = 0;
46 const wchar_t g_Promptphrase[] = L"Enter your OpenSSH passphrase:";
47 const wchar_t* g_Prompt = g_Promptphrase;
49 wchar_t g_PassWord[MAX_LOADSTRING];
51 // Forward declarations of functions included in this code module:
52 INT_PTR CALLBACK PasswdDlg(HWND, UINT, WPARAM, LPARAM);
54 int APIENTRY _tWinMain(HINSTANCE /*hInstance*/,
55 HINSTANCE /*hPrevInstance*/,
56 LPWSTR lpCmdLine,
57 int /*nCmdShow*/)
59 SetDllDirectory(L"");
61 InitCommonControls();
63 size_t cmdlineLen = wcslen(lpCmdLine);
64 if (lpCmdLine[0] == '"' && cmdlineLen > 1 && lpCmdLine[cmdlineLen - 1] == '"')
66 lpCmdLine[cmdlineLen - 1] = L'\0';
67 ++lpCmdLine;
69 if (lpCmdLine[0] != L'\0')
70 g_Prompt = lpCmdLine;
72 if (StrStrI(lpCmdLine, L"(yes/no"))
74 if (CTaskDialog::IsSupported())
76 if (CTaskDialog::ShowDialog(g_Prompt, L"", L"TortoiseGit - Git CLI stdin wrapper", TDCBF_NO_BUTTON | TDCBF_YES_BUTTON, TDF_USE_COMMAND_LINKS | TDF_POSITION_RELATIVE_TO_WINDOW) == IDYES)
77 wprintf(L"yes");
78 else
79 wprintf(L"no");
80 return 0;
83 if (::MessageBox(nullptr, g_Prompt, L"TortoiseGit - Git CLI stdin wrapper", MB_YESNO | MB_ICONQUESTION) == IDYES)
84 wprintf(L"yes");
85 else
86 wprintf(L"no");
87 return 0;
90 if (StrStrI(lpCmdLine, L"Should I try again?"))
92 if (CTaskDialog::IsSupported())
94 if (CTaskDialog::ShowDialog(g_Prompt, L"", L"TortoiseGit - Git CLI yes/no wrapper", TDCBF_NO_BUTTON | TDCBF_YES_BUTTON, TDF_USE_COMMAND_LINKS | TDF_POSITION_RELATIVE_TO_WINDOW) == IDYES)
95 return 0;
96 return 1;
99 if (::MessageBox(nullptr, g_Prompt, L"TortoiseGit - Git CLI yes/no wrapper", MB_YESNO | MB_ICONQUESTION) == IDYES)
100 return 0;
102 return 1;
105 if (DialogBox(hInst, MAKEINTRESOURCE(IDD_ASK_PASSWORD), nullptr, PasswdDlg) == IDOK)
107 auto len = static_cast<int>(_tcslen(g_PassWord));
108 auto size = WideCharToMultiByte(CP_UTF8, 0, g_PassWord, len, nullptr, 0, nullptr, nullptr);
109 auto buf = std::make_unique<char[]>(size + 1);
110 auto ret = WideCharToMultiByte(CP_UTF8, 0, g_PassWord, len, buf.get(), size, nullptr, nullptr);
111 buf[ret] = '\0';
112 printf("%s\n", buf.get());
113 SecureZeroMemory(buf.get(), size + 1);
114 SecureZeroMemory(&g_PassWord, sizeof(g_PassWord));
115 return 0;
117 wprintf(L"\n");
118 return -1;
121 void MarkWindowAsUnpinnable(HWND hWnd)
123 using SHGPSFW = HRESULT(WINAPI*)(HWND hwnd, REFIID riid, void** ppv);
125 CAutoLibrary hShell = AtlLoadSystemLibraryUsingFullPath(L"Shell32.dll");
127 if (hShell) {
128 auto pfnSHGPSFW = reinterpret_cast<SHGPSFW>(::GetProcAddress(hShell, "SHGetPropertyStoreForWindow"));
129 if (pfnSHGPSFW) {
130 IPropertyStore *pps;
131 HRESULT hr = pfnSHGPSFW(hWnd, IID_PPV_ARGS(&pps));
132 if (SUCCEEDED(hr)) {
133 PROPVARIANT var;
134 var.vt = VT_BOOL;
135 var.boolVal = VARIANT_TRUE;
136 pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
137 pps->Release();
143 static SIZE GetTextSize(HWND hWnd, const wchar_t* str)
145 HDC hDC = ::GetWindowDC(hWnd);
146 HFONT font = reinterpret_cast<HFONT>(::SendMessage(hWnd, WM_GETFONT, 0, 0));
147 HFONT oldFont = reinterpret_cast<HFONT>(::SelectObject(hDC, font));
148 RECT r = { 0 };
149 ::DrawText(hDC, str, -1, &r, DT_EDITCONTROL | DT_EXPANDTABS | DT_LEFT | DT_CALCRECT);
150 ::SelectObject(hDC, oldFont);
152 return SIZE{ r.right, r.bottom };
155 static void MoveButton(HWND hDlg, DWORD id, const POINT& diff)
157 RECT rect = { 0 };
158 HWND button = ::GetDlgItem(hDlg, id);
159 ::GetWindowRect(button, &rect);
160 ::MapWindowPoints(nullptr, hDlg, reinterpret_cast<LPPOINT>(&rect), 2);
161 ::MoveWindow(button, rect.left + diff.x / 2, rect.top + diff.y, rect.right - rect.left, rect.bottom - rect.top, TRUE);
164 void SetTheme(HWND hWnd)
166 HIGHCONTRAST hc = { sizeof(HIGHCONTRAST) };
167 SystemParametersInfo(SPI_GETHIGHCONTRAST, sizeof(HIGHCONTRAST), &hc, FALSE);
168 bool isHighContrastMode = ((hc.dwFlags & HCF_HIGHCONTRASTON) != 0);
170 g_darkmode = !isHighContrastMode && DarkModeHelper::Instance().CanHaveDarkMode() && DarkModeHelper::Instance().ShouldAppsUseDarkMode() && CRegStdDWORD(L"Software\\TortoiseGit\\DarkTheme", FALSE) == TRUE;
171 if (g_darkmode)
173 DarkModeHelper::Instance().AllowDarkModeForApp(TRUE);
174 DarkModeHelper::Instance().AllowDarkModeForWindow(hWnd, TRUE);
175 SetClassLongPtr(hWnd, GCLP_HBRBACKGROUND, reinterpret_cast<LONG_PTR>(GetStockObject(BLACK_BRUSH)));
176 if (FAILED(SetWindowTheme(hWnd, L"DarkMode_Explorer", nullptr)))
177 SetWindowTheme(hWnd, L"Explorer", nullptr);
178 BOOL darkFlag = TRUE;
179 DarkModeHelper::WINDOWCOMPOSITIONATTRIBDATA data = { DarkModeHelper::WINDOWCOMPOSITIONATTRIB::WCA_USEDARKMODECOLORS, &darkFlag, sizeof(darkFlag) };
180 DarkModeHelper::Instance().SetWindowCompositionAttribute(hWnd, &data);
181 DarkModeHelper::Instance().FlushMenuThemes();
182 DarkModeHelper::Instance().RefreshImmersiveColorPolicyState();
183 for (UINT id : { IDOK, IDCANCEL })
185 HWND ctrl = ::GetDlgItem(hWnd, id);
186 if (FAILED(SetWindowTheme(ctrl, L"DarkMode_Explorer", nullptr)))
187 SetWindowTheme(ctrl, L"Explorer", nullptr);
190 else
192 DarkModeHelper::Instance().AllowDarkModeForApp(FALSE);
193 DarkModeHelper::Instance().AllowDarkModeForWindow(hWnd, FALSE);
194 SetWindowTheme(hWnd, L"Explorer", nullptr);
195 BOOL darkFlag = FALSE;
196 DarkModeHelper::WINDOWCOMPOSITIONATTRIBDATA data = { DarkModeHelper::WINDOWCOMPOSITIONATTRIB::WCA_USEDARKMODECOLORS, &darkFlag, sizeof(darkFlag) };
197 DarkModeHelper::Instance().SetWindowCompositionAttribute(hWnd, &data);
198 DarkModeHelper::Instance().FlushMenuThemes();
199 DarkModeHelper::Instance().RefreshImmersiveColorPolicyState();
200 DarkModeHelper::Instance().AllowDarkModeForApp(FALSE);
201 SetClassLongPtr(hWnd, GCLP_HBRBACKGROUND, reinterpret_cast<LONG_PTR>(GetSysColorBrush(COLOR_3DFACE)));
202 for (UINT id : { IDOK, IDCANCEL })
203 SetWindowTheme(::GetDlgItem(hWnd, id), L"Explorer", nullptr);
206 ::RedrawWindow(hWnd, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE | RDW_ERASE | RDW_INTERNALPAINT | RDW_ALLCHILDREN | RDW_UPDATENOW);
209 // Message handler for password box.
210 INT_PTR CALLBACK PasswdDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
212 static HBRUSH hbrBkgnd = nullptr;
213 switch (message)
215 case WM_INITDIALOG:
217 MarkWindowAsUnpinnable(hDlg);
218 SetTheme(hDlg);
219 m_dpi = CDPIAware::Instance().GetDPI(hDlg);
221 RECT rect;
222 ::GetWindowRect(hDlg,&rect);
223 DWORD dwWidth = GetSystemMetrics(SM_CXSCREEN);
224 DWORD dwHeight = GetSystemMetrics(SM_CYSCREEN);
226 HWND title = ::GetDlgItem(hDlg, IDC_STATIC_TITLE);
227 ::SetWindowText(title, g_Prompt);
228 RECT titleRect = { 0 };
229 ::GetClientRect(title, &titleRect);
230 auto promptRect = GetTextSize(title, g_Prompt);
231 POINT diff = { 0 };
232 diff.y = max(0, promptRect.cy - titleRect.bottom);
233 diff.x = max(0, promptRect.cx - titleRect.right);
234 ::SetWindowPos(title, nullptr, 0, 0, titleRect.right + diff.x, titleRect.bottom + diff.y, SWP_NOMOVE);
236 HWND textfield = ::GetDlgItem(hDlg, IDC_PASSWORD);
237 RECT textfieldRect = { 0 };
238 ::GetWindowRect(textfield, &textfieldRect);
239 ::MapWindowPoints(nullptr, hDlg, reinterpret_cast<LPPOINT>(&textfieldRect), 2);
240 ::MoveWindow(textfield, textfieldRect.left, textfieldRect.top + diff.y, textfieldRect.right - textfieldRect.left + diff.x, textfieldRect.bottom - textfieldRect.top, TRUE);
242 MoveButton(hDlg, IDOK, diff);
243 MoveButton(hDlg, IDCANCEL, diff);
245 DWORD x = (dwWidth - (rect.right - rect.left + diff.x)) / 2;
246 DWORD y = (dwHeight - (rect.bottom - rect.top + diff.y)) / 2;
247 ::MoveWindow(hDlg, x, y, rect.right - rect.left + diff.x, rect.bottom - rect.top + diff.y, TRUE);
248 ::SendMessage(textfield, EM_SETLIMITTEXT, MAX_LOADSTRING - 1, 0);
249 if (!StrStrI(g_Prompt, L"pass"))
250 ::SendMessage(textfield, EM_SETPASSWORDCHAR, 0, 0);
251 ::ShowWindow(hDlg, SW_SHOW);
252 ::FlashWindow(hDlg, TRUE);
254 return TRUE;
256 case WM_CTLCOLORDLG:
257 case WM_CTLCOLOREDIT:
258 case WM_CTLCOLORSTATIC:
259 if (g_darkmode)
261 constexpr COLORREF darkBkColor = 0x202020; // cf. THeme.h
262 constexpr COLORREF darkTextColor = 0xDDDDDD;
264 HDC hdc = reinterpret_cast<HDC>(wParam);
265 SetTextColor(hdc, darkTextColor);
266 SetBkColor(hdc, darkBkColor);
267 if (!hbrBkgnd)
268 hbrBkgnd = CreateSolidBrush(darkBkColor);
269 return reinterpret_cast<INT_PTR>(hbrBkgnd);
271 break;
273 case WM_DESTROY:
274 if (hbrBkgnd)
276 ::DeleteObject(hbrBkgnd);
277 hbrBkgnd = nullptr;
279 break;
281 case WM_SYSCOLORCHANGE:
282 SetTheme(hDlg);
283 break;
285 case WM_DPICHANGED:
287 CDPIAware::Instance().Invalidate();
288 const auto newDPI = CDPIAware::Instance().GetDPI(hDlg);
289 RECT* rect = reinterpret_cast<RECT*>(lParam);
290 RECT oldRect{};
291 GetWindowRect(hDlg, &oldRect);
292 const double zoom = (static_cast<double>(newDPI) / (static_cast<double>(m_dpi) / 100.0)) / 100.0;
293 rect->right = static_cast<LONG>(rect->left + (oldRect.right - oldRect.left) * zoom);
294 rect->bottom = static_cast<LONG>(rect->top + (oldRect.bottom - oldRect.top) * zoom);
295 const CDPIAware::DPIAdjustData data{ hDlg, zoom };
296 ::SetWindowPos(hDlg, nullptr, rect->left, rect->top, rect->right - rect->left, rect->bottom - rect->top, SWP_NOZORDER | SWP_NOACTIVATE);
297 ::EnumChildWindows(hDlg, CDPIAware::DPIAdjustChildren, reinterpret_cast<LPARAM>(&data));
298 ::RedrawWindow(hDlg, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE | RDW_ERASE | RDW_INTERNALPAINT | RDW_ALLCHILDREN | RDW_UPDATENOW);
299 m_dpi = newDPI;
300 break;
303 case WM_COMMAND:
305 if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
307 HWND password = ::GetDlgItem(hDlg, IDC_PASSWORD);
308 if (LOWORD(wParam) == IDOK)
309 ::GetWindowText(password, g_PassWord, MAX_LOADSTRING);
311 // overwrite textfield contents with garbage in order to wipe the cache
312 wchar_t gargabe[MAX_LOADSTRING];
313 wmemset(gargabe, L'*', _countof(gargabe));
314 gargabe[_countof(gargabe) - 1] = L'\0';
315 ::SetWindowText(password, gargabe);
316 gargabe[0] = L'\0';
317 ::SetWindowText(password, gargabe);
319 EndDialog(hDlg, LOWORD(wParam));
320 return TRUE;
322 break;
324 return FALSE;