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.
27 #include "UnicodeUtils.h"
28 #include "SmartHandle.h"
30 #include "DarkModeHelper.h"
33 #include <afxtaskdialog.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
42 HINSTANCE hInst
; // current instance
43 bool g_darkmode
= false;
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*/,
63 size_t cmdlineLen
= wcslen(lpCmdLine
);
64 if (lpCmdLine
[0] == '"' && cmdlineLen
> 1 && lpCmdLine
[cmdlineLen
- 1] == '"')
66 lpCmdLine
[cmdlineLen
- 1] = L
'\0';
69 if (lpCmdLine
[0] != L
'\0')
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
)
83 if (::MessageBox(nullptr, g_Prompt
, L
"TortoiseGit - Git CLI stdin wrapper", MB_YESNO
| MB_ICONQUESTION
) == IDYES
)
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
)
99 if (::MessageBox(nullptr, g_Prompt
, L
"TortoiseGit - Git CLI yes/no wrapper", MB_YESNO
| MB_ICONQUESTION
) == IDYES
)
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);
112 printf("%s\n", buf
.get());
113 SecureZeroMemory(buf
.get(), size
+ 1);
114 SecureZeroMemory(&g_PassWord
, sizeof(g_PassWord
));
121 void MarkWindowAsUnpinnable(HWND hWnd
)
123 using SHGPSFW
= HRESULT(WINAPI
*)(HWND hwnd
, REFIID riid
, void** ppv
);
125 CAutoLibrary hShell
= AtlLoadSystemLibraryUsingFullPath(L
"Shell32.dll");
128 auto pfnSHGPSFW
= reinterpret_cast<SHGPSFW
>(::GetProcAddress(hShell
, "SHGetPropertyStoreForWindow"));
131 HRESULT hr
= pfnSHGPSFW(hWnd
, IID_PPV_ARGS(&pps
));
135 var
.boolVal
= VARIANT_TRUE
;
136 pps
->SetValue(PKEY_AppUserModel_PreventPinning
, var
);
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
));
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
)
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
;
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);
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;
217 MarkWindowAsUnpinnable(hDlg
);
219 m_dpi
= CDPIAware::Instance().GetDPI(hDlg
);
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
);
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
);
257 case WM_CTLCOLOREDIT
:
258 case WM_CTLCOLORSTATIC
:
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
);
268 hbrBkgnd
= CreateSolidBrush(darkBkColor
);
269 return reinterpret_cast<INT_PTR
>(hbrBkgnd
);
276 ::DeleteObject(hbrBkgnd
);
281 case WM_SYSCOLORCHANGE
:
287 CDPIAware::Instance().Invalidate();
288 const auto newDPI
= CDPIAware::Instance().GetDPI(hDlg
);
289 RECT
* rect
= reinterpret_cast<RECT
*>(lParam
);
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
);
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
);
317 ::SetWindowText(password
, gargabe
);
319 EndDialog(hDlg
, LOWORD(wParam
));