1 // SciTE - Scintilla based Text Editor
2 /** @file SciTEWin.cxx
3  ** Main code for the Windows version of the editor.
4  **/
5 // Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7 
8 #include <time.h>
9 
10 #include "SciTEWin.h"
11 #include "DLLFunction.h"
12 
13 #ifndef WM_DPICHANGED
14 #define WM_DPICHANGED 0x02E0
15 #endif
16 
17 #ifndef NO_EXTENSIONS
18 #include "MultiplexExtension.h"
19 
20 #ifndef NO_FILER
21 #include "DirectorExtension.h"
22 #endif
23 
24 #ifndef NO_LUA
25 #include "LuaExtension.h"
26 #endif
27 
28 #endif
29 
30 #ifdef STATIC_BUILD
31 const GUI::gui_char appName[] = GUI_TEXT("Sc1");
32 #else
33 const GUI::gui_char appName[] = GUI_TEXT("SciTE");
34 #ifdef LOAD_SCINTILLA
35 static const GUI::gui_char scintillaName[] = GUI_TEXT("Scintilla.DLL");
36 #else
37 static const GUI::gui_char scintillaName[] = GUI_TEXT("SciLexer.DLL");
38 #endif
39 #endif
40 
GetErrorMessage(DWORD nRet)41 static GUI::gui_string GetErrorMessage(DWORD nRet) {
42 	LPWSTR lpMsgBuf = nullptr;
43 	if (::FormatMessage(
44 				FORMAT_MESSAGE_ALLOCATE_BUFFER |
45 				FORMAT_MESSAGE_FROM_SYSTEM |
46 				FORMAT_MESSAGE_IGNORE_INSERTS,
47 				nullptr,
48 				nRet,
49 				MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),   // Default language
50 				reinterpret_cast<LPWSTR>(&lpMsgBuf),
51 				0,
52 				nullptr
53 			) != 0) {
54 		GUI::gui_string s= lpMsgBuf;
55 		::LocalFree(lpMsgBuf);
56 		return s;
57 	} else {
58 		return TEXT("");
59 	}
60 }
61 
ParseKeyCode(const char * mnemonic)62 long SciTEKeys::ParseKeyCode(const char *mnemonic) {
63 	SA::KeyMod modsInKey = static_cast<SA::KeyMod>(0);
64 	int keyval = -1;
65 
66 	if (mnemonic && *mnemonic) {
67 		std::string sKey = mnemonic;
68 
69 		if (RemoveStringOnce(sKey, "Ctrl+"))
70 			modsInKey = modsInKey | SA::KeyMod::Ctrl;
71 		if (RemoveStringOnce(sKey, "Shift+"))
72 			modsInKey = modsInKey | SA::KeyMod::Shift;
73 		if (RemoveStringOnce(sKey, "Alt+"))
74 			modsInKey = modsInKey | SA::KeyMod::Alt;
75 
76 		if (sKey.length() == 1) {
77 			keyval = VkKeyScan(sKey.at(0)) & 0xFF;
78 		} else if (sKey.length() > 1) {
79 			if ((sKey.at(0) == 'F') && (IsADigit(sKey.at(1)))) {
80 				sKey.erase(0, 1);
81 				const int fkeyNum = atoi(sKey.c_str());
82 				if (fkeyNum >= 1 && fkeyNum <= 12)
83 					keyval = fkeyNum - 1 + VK_F1;
84 			} else if ((sKey.at(0) == 'V') && (IsADigit(sKey.at(1)))) {
85 				sKey.erase(0, 1);
86 				const int vkey = atoi(sKey.c_str());
87 				if (vkey > 0 && vkey <= 0x7FFF)
88 					keyval = vkey;
89 			} else if (StartsWith(sKey, "Keypad")) {
90 				sKey.erase(0, strlen("Keypad"));
91 				if ((sKey.length() > 0) && IsADigit(sKey.at(0))) {
92 					const int keyNum = atoi(sKey.c_str());
93 					if (keyNum >= 0 && keyNum <= 9)
94 						keyval = keyNum + VK_NUMPAD0;
95 				} else if (sKey == "Plus") {
96 					keyval = VK_ADD;
97 				} else if (sKey == "Minus") {
98 					keyval = VK_SUBTRACT;
99 				} else if (sKey == "Decimal") {
100 					keyval = VK_DECIMAL;
101 				} else if (sKey == "Divide") {
102 					keyval = VK_DIVIDE;
103 				} else if (sKey == "Multiply") {
104 					keyval = VK_MULTIPLY;
105 				}
106 			} else if (sKey == "Left") {
107 				keyval = VK_LEFT;
108 			} else if (sKey == "Right") {
109 				keyval = VK_RIGHT;
110 			} else if (sKey == "Up") {
111 				keyval = VK_UP;
112 			} else if (sKey == "Down") {
113 				keyval = VK_DOWN;
114 			} else if (sKey == "Insert") {
115 				keyval = VK_INSERT;
116 			} else if (sKey == "End") {
117 				keyval = VK_END;
118 			} else if (sKey == "Home") {
119 				keyval = VK_HOME;
120 			} else if (sKey == "Enter") {
121 				keyval = VK_RETURN;
122 			} else if (sKey == "Space") {
123 				keyval = VK_SPACE;
124 			} else if (sKey == "Tab") {
125 				keyval = VK_TAB;
126 			} else if (sKey == "Escape") {
127 				keyval = VK_ESCAPE;
128 			} else if (sKey == "Delete") {
129 				keyval = VK_DELETE;
130 			} else if (sKey == "PageUp") {
131 				keyval = VK_PRIOR;
132 			} else if (sKey == "PageDown") {
133 				keyval = VK_NEXT;
134 			} else if (sKey == "Win") {
135 				keyval = VK_LWIN;
136 			} else if (sKey == "Menu") {
137 				keyval = VK_APPS;
138 			} else if (sKey == "Backward") {
139 				keyval = VK_BROWSER_BACK;
140 			} else if (sKey == "Forward") {
141 				keyval = VK_BROWSER_FORWARD;
142 			}
143 		}
144 	}
145 
146 	return (keyval > 0) ? (keyval | (static_cast<int>(modsInKey)<<16)) : 0;
147 }
148 
MatchKeyCode(long parsedKeyCode,int keyval,int modifiers)149 bool SciTEKeys::MatchKeyCode(long parsedKeyCode, int keyval, int modifiers) noexcept {
150 	return parsedKeyCode && !(0xFFFF0000 & (keyval | modifiers)) && (parsedKeyCode == (keyval | (modifiers<<16)));
151 }
152 
153 HINSTANCE SciTEWin::hInstance {};
154 const TCHAR *SciTEWin::className = nullptr;
155 const TCHAR *SciTEWin::classNameInternal = nullptr;
156 SciTEWin *SciTEWin::app = nullptr;
157 
158 namespace {
159 
160 using SystemParametersInfoForDpiSig = BOOL (WINAPI *)(
161 	UINT  uiAction,
162 	UINT  uiParam,
163 	PVOID pvParam,
164 	UINT  fWinIni,
165 	UINT  dpi
166 );
167 
168 SystemParametersInfoForDpiSig fnSystemParametersInfoForDpi;
169 
170 // Using VerifyVersionInfo on Windows 10 will pretend its Windows 8
171 // but that is good enough for switching UI elements to flatter.
172 // The VersionHelpers.h functions can't be used as they aren't supported by GCC.
173 
UIShouldBeFlat()174 bool UIShouldBeFlat() noexcept {
175 	OSVERSIONINFOEX osvi = OSVERSIONINFOEX();
176 	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
177 	osvi.dwMajorVersion = 6;
178 	osvi.dwMinorVersion = 2;
179 
180 	constexpr BYTE op = VER_GREATER_EQUAL;
181 	DWORDLONG dwlConditionMask = 0;
182 	VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, op);
183 	VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, op);
184 
185 	return VerifyVersionInfo(
186 		       &osvi,
187 		       VER_MAJORVERSION | VER_MINORVERSION |
188 		       VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR,
189 		       dwlConditionMask);
190 }
191 
192 }
193 
SciTEWin(Extension * ext)194 SciTEWin::SciTEWin(Extension *ext) : SciTEBase(ext) {
195 	app = this;
196 
197 	contents.SetSciTE(this);
198 	contents.SetLocalizer(&localiser);
199 	backgroundStrip.SetLocalizer(&localiser);
200 	searchStrip.SetLocalizer(&localiser);
201 	searchStrip.SetSearcher(this);
202 	findStrip.SetLocalizer(&localiser);
203 	findStrip.SetSearcher(this);
204 	replaceStrip.SetLocalizer(&localiser);
205 	replaceStrip.SetSearcher(this);
206 
207 	flatterUI = UIShouldBeFlat();
208 
209 	appearance = CurrentAppearance();
210 
211 	cmdShow = 0;
212 	heightBar = 7;
213 	fontTabs = {};
214 	wFocus = {};
215 
216 	winPlace = WINDOWPLACEMENT();
217 	winPlace.length = 0;
218 	rcWorkArea = RECT();
219 
220 	openWhat[0] = '\0';
221 	tooltipText[0] = '\0';
222 	tbLarge = false;
223 	modalParameters = false;
224 	filterDefault = 1;
225 	staticBuild = false;
226 	menuSource = 0;
227 
228 	hWriteSubProcess = {};
229 	subProcessGroupId = 0;
230 
231 	pathAbbreviations = GetAbbrevPropertiesFileName();
232 
233 	// System type properties are stored in the platform properties.
234 	propsPlatform.Set("PLAT_WIN", "1");
235 	propsPlatform.Set("PLAT_WINNT", "1");
236 
237 	ReadEnvironment();
238 
239 	SetScaleFactor(GetScaleFactor());
240 
241 	ReadGlobalPropFile();
242 
243 	if (props.GetInt("create.hidden.console")) {
244 		// Prevent a flashing console window whenever Lua calls os.execute or
245 		// io.popen by creating a hidden console to share.
246 		::AllocConsole();
247 		::ShowWindow(::GetConsoleWindow(), SW_HIDE);
248 	}
249 
250 	tbLarge = props.GetInt("toolbar.large");
251 	/// Need to copy properties to variables before setting up window
252 	SetPropertiesInitial();
253 	ReadAbbrevPropFile();
254 
255 	hDevMode = {};
256 	hDevNames = {};
257 	::ZeroMemory(&pagesetupMargin, sizeof(pagesetupMargin));
258 
259 	hHH = {};
260 	hMM = {};
261 	uniqueInstance.Init(this);
262 
263 	hAccTable = ::LoadAccelerators(hInstance, TEXT("ACCELS")); // md
264 
265 	cmdWorker.pSciTE = this;
266 }
267 
~SciTEWin()268 SciTEWin::~SciTEWin() {
269 	if (hDevMode)
270 		::GlobalFree(hDevMode);
271 	if (hDevNames)
272 		::GlobalFree(hDevNames);
273 	if (hHH)
274 		::FreeLibrary(hHH);
275 	if (hMM)
276 		::FreeLibrary(hMM);
277 	if (fontTabs)
278 		::DeleteObject(fontTabs);
279 	if (hAccTable)
280 		::DestroyAcceleratorTable(hAccTable);
281 }
282 
GetInstance()283 uintptr_t SciTEWin::GetInstance() {
284 	return reinterpret_cast<uintptr_t>(hInstance);
285 }
286 
Register(HINSTANCE hInstance_)287 void SciTEWin::Register(HINSTANCE hInstance_) {
288 	const TCHAR resourceName[] = TEXT("SciTE");
289 
290 	hInstance = hInstance_;
291 
292 	WNDCLASS wndclass {};
293 
294 	// Register the frame window
295 	className = TEXT("SciTEWindow");
296 	wndclass.style = 0;
297 	wndclass.lpfnWndProc = SciTEWin::TWndProc;
298 	wndclass.cbClsExtra = 0;
299 	wndclass.cbWndExtra = sizeof(SciTEWin *);
300 	wndclass.hInstance = hInstance;
301 	wndclass.hIcon = ::LoadIcon(hInstance, resourceName);
302 	wndclass.hCursor = {};
303 	wndclass.hbrBackground = {};
304 	wndclass.lpszMenuName = resourceName;
305 	wndclass.lpszClassName = className;
306 	if (!::RegisterClass(&wndclass))
307 		exit(FALSE);
308 
309 	// Register the window that holds the two Scintilla edit windows and the separator
310 	classNameInternal = TEXT("SciTEWindowContent");
311 	wndclass.lpfnWndProc = BaseWin::StWndProc;
312 	wndclass.lpszMenuName = nullptr;
313 	wndclass.lpszClassName = classNameInternal;
314 	if (!::RegisterClass(&wndclass))
315 		exit(FALSE);
316 }
317 
CodePageFromName(const std::string & encodingName)318 static int CodePageFromName(const std::string &encodingName) {
319 	struct Encoding {
320 		const char *name;
321 		int codePage;
322 	} knownEncodings[] = {
323 		{ "ascii", CP_UTF8 },
324 		{ "utf-8", CP_UTF8 },
325 		{ "latin1", 1252 },
326 		{ "latin2", 28592 },
327 		{ "big5", 950 },
328 		{ "gbk", 936 },
329 		{ "shift_jis", 932 },
330 		{ "euc-kr", 949 },
331 		{ "cyrillic", 1251 },
332 		{ "iso-8859-5", 28595 },
333 		{ "iso8859-11", 874 },
334 		{ "1250", 1250 },
335 		{ "windows-1251", 1251 },
336 	};
337 	for (const Encoding &enc : knownEncodings) {
338 		if (encodingName == enc.name) {
339 			return enc.codePage;
340 		}
341 	}
342 	return CP_UTF8;
343 }
344 
StringEncode(std::wstring s,int codePage)345 static std::string StringEncode(std::wstring s, int codePage) {
346 	if (s.length()) {
347 		const int sLength = static_cast<int>(s.length());
348 		const int cchMulti = ::WideCharToMultiByte(codePage, 0, s.c_str(), sLength, nullptr, 0, nullptr, nullptr);
349 		std::string sMulti(cchMulti, 0);
350 		::WideCharToMultiByte(codePage, 0, s.c_str(), sLength, &sMulti[0], cchMulti, nullptr, nullptr);
351 		return sMulti;
352 	} else {
353 		return std::string();
354 	}
355 }
356 
StringDecode(std::string s,int codePage)357 static std::wstring StringDecode(std::string s, int codePage) {
358 	if (s.length()) {
359 		const int sLength = static_cast<int>(s.length());
360 		const int cchWide = ::MultiByteToWideChar(codePage, 0, s.c_str(), sLength, nullptr, 0);
361 		std::wstring sWide(cchWide, 0);
362 		::MultiByteToWideChar(codePage, 0, s.c_str(), sLength, &sWide[0], cchWide);
363 		return sWide;
364 	} else {
365 		return std::wstring();
366 	}
367 }
368 
369 // Convert to UTF-8
ConvertEncoding(const char * original,int codePage)370 static std::string ConvertEncoding(const char *original, int codePage) {
371 	if (codePage == CP_UTF8) {
372 		return original;
373 	} else {
374 		GUI::gui_string sWide = StringDecode(std::string(original), codePage);
375 		return GUI::UTF8FromString(sWide);
376 	}
377 }
378 
ReadLocalization()379 void SciTEWin::ReadLocalization() {
380 	SciTEBase::ReadLocalization();
381 	std::string encoding = localiser.GetString("translation.encoding");
382 	LowerCaseAZ(encoding);
383 	if (encoding.length()) {
384 		const int codePageNamed = CodePageFromName(encoding);
385 		const char *key = nullptr;
386 		const char *val = nullptr;
387 		// Get encoding
388 		bool more = localiser.GetFirst(key, val);
389 		while (more) {
390 			std::string converted = ConvertEncoding(val, codePageNamed);
391 			if (converted != "") {
392 				localiser.Set(key, converted.c_str());
393 			}
394 			more = localiser.GetNext(key, val);
395 		}
396 	}
397 }
398 
GetWindowPosition(int * left,int * top,int * width,int * height,int * maximize)399 void SciTEWin::GetWindowPosition(int *left, int *top, int *width, int *height, int *maximize) {
400 	winPlace.length = sizeof(winPlace);
401 	::GetWindowPlacement(MainHWND(), &winPlace);
402 
403 	*left = winPlace.rcNormalPosition.left;
404 	*top = winPlace.rcNormalPosition.top;
405 	*width =  winPlace.rcNormalPosition.right - winPlace.rcNormalPosition.left;
406 	*height = winPlace.rcNormalPosition.bottom - winPlace.rcNormalPosition.top;
407 	*maximize = (winPlace.showCmd == SW_MAXIMIZE) ? 1 : 0;
408 }
409 
GetScaleFactor()410 int SciTEWin::GetScaleFactor() noexcept {
411 	// Only called at startup, so just use the default for a DC
412 	HDC hdcMeasure = ::CreateCompatibleDC({});
413 	const int scale = ::GetDeviceCaps(hdcMeasure, LOGPIXELSY) * 100 / 96;
414 	::DeleteDC(hdcMeasure);
415 	return scale;
416 }
417 
SetScaleFactor(int scale)418 bool SciTEWin::SetScaleFactor(int scale) {
419 	const std::string sScale = StdStringFromInteger(scale);
420 	const std::string sCurrentScale = propsPlatform.GetString("ScaleFactor");
421 	if (sScale == sCurrentScale) {
422 		return false;
423 	}
424 	propsPlatform.Set("ScaleFactor", sScale);
425 	return true;
426 }
427 
428 // Allow UTF-8 file names and command lines to be used in calls to io.open and io.popen in Lua scripts.
429 // The scite_lua_win.h header redirects fopen and _popen to these functions.
430 
431 extern "C" {
432 
scite_lua_fopen(const char * filename,const char * mode)433 	FILE *scite_lua_fopen(const char *filename, const char *mode) {
434 		GUI::gui_string sFilename = GUI::StringFromUTF8(filename);
435 		GUI::gui_string sMode = GUI::StringFromUTF8(mode);
436 		FILE *f = _wfopen(sFilename.c_str(), sMode.c_str());
437 		if (!f)
438 			// Fallback on narrow string in case already in CP_ACP
439 			f = fopen(filename, mode);
440 		return f;
441 	}
442 
scite_lua_popen(const char * filename,const char * mode)443 	FILE *scite_lua_popen(const char *filename, const char *mode) {
444 		GUI::gui_string sFilename = GUI::StringFromUTF8(filename);
445 		GUI::gui_string sMode = GUI::StringFromUTF8(mode);
446 		FILE *f = _wpopen(sFilename.c_str(), sMode.c_str());
447 		if (!f)
448 			// Fallback on narrow string in case already in CP_ACP
449 			f = _popen(filename, mode);
450 		return f;
451 	}
452 
453 }
454 
455 // Read properties resource into propsEmbed
456 // The embedded properties file is to allow distributions to be sure
457 // that they have a sensible default configuration even if the properties
458 // files are missing. Also allows a one file distribution of Sc1.EXE.
ReadEmbeddedProperties()459 void SciTEWin::ReadEmbeddedProperties() {
460 
461 	propsEmbed.Clear();
462 
463 	HRSRC handProps = ::FindResource(hInstance, TEXT("Embedded"), TEXT("Properties"));
464 	if (handProps) {
465 		const DWORD size = ::SizeofResource(hInstance, handProps);
466 		HGLOBAL hmem = ::LoadResource(hInstance, handProps);
467 		if (hmem) {
468 			const void *pv = ::LockResource(hmem);
469 			if (pv) {
470 				propsEmbed.ReadFromMemory(
471 					static_cast<const char *>(pv), size, FilePath(), filter, NULL, 0);
472 			}
473 		}
474 		::FreeResource(handProps);
475 	}
476 }
477 
CurrentAppearance() const478 SystemAppearance SciTEWin::CurrentAppearance() const noexcept {
479 	SystemAppearance currentAppearance{};
480 
481 	HKEY hkeyPersonalize{};
482 	const LSTATUS statusOpen = ::RegOpenKeyExW(HKEY_CURRENT_USER,
483 		L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
484 		0, KEY_QUERY_VALUE, &hkeyPersonalize);
485 	if (statusOpen == ERROR_SUCCESS) {
486 		DWORD type = 0;
487 		DWORD val = 99;
488 		DWORD cbData = sizeof(val);
489 		const LSTATUS status = ::RegQueryValueExW(hkeyPersonalize, L"AppsUseLightTheme", nullptr,
490 			&type, reinterpret_cast<LPBYTE>(&val), reinterpret_cast<LPDWORD>(&cbData));
491 		RegCloseKey(hkeyPersonalize);
492 		if (status == ERROR_SUCCESS) {
493 			currentAppearance.dark = val == 0;
494 		}
495 	}
496 
497 	HIGHCONTRAST info{};
498 	info.cbSize = sizeof(HIGHCONTRAST)
499 		;
500 	const BOOL status = SystemParametersInfoW(SPI_GETHIGHCONTRAST, 0, &info, 0);
501 	if (status) {
502 		currentAppearance.highContrast = (info.dwFlags & HCF_HIGHCONTRASTON) ? 1 : 0;
503 		if (currentAppearance.highContrast) {
504 			// With high contrast, AppsUseLightTheme not correct so examine system background colour
505 			const DWORD dwWindowColour = ::GetSysColor(COLOR_WINDOW);
506 			currentAppearance.dark = dwWindowColour < 0x40;
507 		}
508 	}
509 
510 	return currentAppearance;
511 }
512 
ReadPropertiesInitial()513 void SciTEWin::ReadPropertiesInitial() {
514 	SciTEBase::ReadPropertiesInitial();
515 	if (tabMultiLine) {	// Windows specific!
516 		const long wl = GetWindowStyle(HwndOf(wTabBar));
517 		::SetWindowLong(HwndOf(wTabBar), GWL_STYLE, wl | TCS_MULTILINE);
518 	}
519 }
520 
ReadProperties()521 void SciTEWin::ReadProperties() {
522 	SciTEBase::ReadProperties();
523 	if (flatterUI) {
524 		if (foldColour.empty() && foldHiliteColour.empty()) {
525 			constexpr SA::Colour lightMargin = ColourRGB(0xF7, 0xF7, 0xF7);
526 			CallChildren(SA::Message::SetFoldMarginColour, 1, lightMargin);
527 			CallChildren(SA::Message::SetFoldMarginHiColour, 1, lightMargin);
528 		}
529 	}
530 }
531 
GetSciTEPath(const FilePath & home)532 static FilePath GetSciTEPath(const FilePath &home) {
533 	if (home.IsSet()) {
534 		return FilePath(home);
535 	} else {
536 		GUI::gui_char path[MAX_PATH];
537 		if (::GetModuleFileNameW(0, path, static_cast<DWORD>(std::size(path))) == 0)
538 			return FilePath();
539 		// Remove the SciTE.exe
540 		GUI::gui_char *lastSlash = wcsrchr(path, pathSepChar);
541 		if (lastSlash)
542 			*lastSlash = '\0';
543 		return FilePath(path);
544 	}
545 }
546 
GetDefaultDirectory()547 FilePath SciTEWin::GetDefaultDirectory() {
548 	const GUI::gui_char *home = _wgetenv(GUI_TEXT("SciTE_HOME"));
549 	return GetSciTEPath(home);
550 }
551 
GetSciteDefaultHome()552 FilePath SciTEWin::GetSciteDefaultHome() {
553 	const GUI::gui_char *home = _wgetenv(GUI_TEXT("SciTE_HOME"));
554 	return GetSciTEPath(home);
555 }
556 
GetSciteUserHome()557 FilePath SciTEWin::GetSciteUserHome() {
558 	// First looking for environment variable $SciTE_USERHOME
559 	// to set SciteUserHome. If not present we look for $SciTE_HOME
560 	// then defaulting to $USERPROFILE
561 	GUI::gui_char *home = _wgetenv(GUI_TEXT("SciTE_USERHOME"));
562 	if (!home) {
563 		home = _wgetenv(GUI_TEXT("SciTE_HOME"));
564 		if (!home) {
565 			home = _wgetenv(GUI_TEXT("USERPROFILE"));
566 		}
567 	}
568 	return GetSciTEPath(home);
569 }
570 
571 // Help command lines contain topic!path
ExecuteOtherHelp(const char * cmd)572 void SciTEWin::ExecuteOtherHelp(const char *cmd) {
573 	GUI::gui_string s = GUI::StringFromUTF8(cmd);
574 	const size_t pos = s.find_first_of('!');
575 	if (pos != GUI::gui_string::npos) {
576 		GUI::gui_string topic = s.substr(0, pos);
577 		GUI::gui_string path = s.substr(pos+1);
578 		::WinHelpW(MainHWND(),
579 			   path.c_str(),
580 			   HELP_KEY,
581 			   reinterpret_cast<ULONG_PTR>(topic.c_str()));
582 	}
583 }
584 
585 // HH_AKLINK not in mingw headers
586 struct XHH_AKLINK {
587 	long cbStruct;
588 	BOOL fReserved;
589 	const wchar_t *pszKeywords;
590 	wchar_t *pszUrl;
591 	wchar_t *pszMsgText;
592 	wchar_t *pszMsgTitle;
593 	wchar_t *pszWindow;
594 	BOOL fIndexOnFail;
595 };
596 
597 // Help command lines contain topic!path
ExecuteHelp(const char * cmd)598 void SciTEWin::ExecuteHelp(const char *cmd) {
599 	if (!hHH)
600 		hHH = ::LoadLibrary(TEXT("HHCTRL.OCX"));
601 
602 	if (hHH) {
603 		GUI::gui_string s = GUI::StringFromUTF8(cmd);
604 		const size_t pos = s.find_first_of('!');
605 		if (pos != GUI::gui_string::npos) {
606 			GUI::gui_string topic = s.substr(0, pos);
607 			GUI::gui_string path = s.substr(pos + 1);
608 			typedef HWND (WINAPI *HelpFn)(HWND, const wchar_t *, UINT, DWORD_PTR);
609 			HelpFn fnHHW = reinterpret_cast<HelpFn>(::GetProcAddress(hHH, "HtmlHelpW"));
610 			if (fnHHW) {
611 				XHH_AKLINK ak;
612 				ak.cbStruct = sizeof(ak);
613 				ak.fReserved = FALSE;
614 				ak.pszKeywords = topic.c_str();
615 				ak.pszUrl = nullptr;
616 				ak.pszMsgText = nullptr;
617 				ak.pszMsgTitle = nullptr;
618 				ak.pszWindow = nullptr;
619 				ak.fIndexOnFail = TRUE;
620 				fnHHW(NULL,
621 				      path.c_str(),
622 				      0x000d,          	// HH_KEYWORD_LOOKUP
623 				      reinterpret_cast<DWORD_PTR>(&ak)
624 				     );
625 			}
626 		}
627 	}
628 }
629 
CopyAsRTF()630 void SciTEWin::CopyAsRTF() {
631 	const SA::Range cr = GetSelection();
632 	std::ostringstream oss;
633 	SaveToStreamRTF(oss, cr.start, cr.end);
634 	const std::string rtf = oss.str();
635 	const size_t len = rtf.length() + 1;	// +1 for NUL
636 	HGLOBAL hand = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, len);
637 	if (hand) {
638 		::OpenClipboard(MainHWND());
639 		::EmptyClipboard();
640 		char *ptr = static_cast<char *>(::GlobalLock(hand));
641 		if (ptr) {
642 			memcpy(ptr, rtf.c_str(), len);
643 			::GlobalUnlock(hand);
644 		}
645 		::SetClipboardData(::RegisterClipboardFormat(CF_RTF), hand);
646 		::CloseClipboard();
647 	}
648 }
649 
CopyPath()650 void SciTEWin::CopyPath() {
651 	if (filePath.IsUntitled())
652 		return;
653 
654 	const GUI::gui_string clipText(filePath.AsInternal());
655 	const size_t blobSize = sizeof(GUI::gui_char)*(clipText.length()+1);
656 	if (::OpenClipboard(MainHWND())) {
657 		HGLOBAL hand = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, blobSize);
658 		if (hand) {
659 			::EmptyClipboard();
660 			GUI::gui_char *ptr = static_cast<GUI::gui_char *>(::GlobalLock(hand));
661 			if (ptr) {
662 				memcpy(ptr, clipText.c_str(), blobSize);
663 				::GlobalUnlock(hand);
664 			}
665 			::SetClipboardData(CF_UNICODETEXT, hand);
666 		}
667 		::CloseClipboard();
668 	}
669 }
670 
FullScreenToggle()671 void SciTEWin::FullScreenToggle() {
672 	HWND wTaskBar = FindWindow(TEXT("Shell_TrayWnd"), TEXT(""));
673 	HWND wStartButton = FindWindow(TEXT("Button"), nullptr);
674 	fullScreen = !fullScreen;
675 	if (fullScreen) {
676 		::SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWorkArea, 0);
677 		::SystemParametersInfo(SPI_SETWORKAREA, 0, nullptr, SPIF_SENDCHANGE);
678 		if (wStartButton)
679 			::ShowWindow(wStartButton, SW_HIDE);
680 		::ShowWindow(wTaskBar, SW_HIDE);
681 
682 		winPlace.length = sizeof(winPlace);
683 		::GetWindowPlacement(MainHWND(), &winPlace);
684 		int topStuff = ::GetSystemMetrics(SM_CYSIZEFRAME) + ::GetSystemMetrics(SM_CYCAPTION);
685 		if (props.GetInt("full.screen.hides.menu"))
686 			topStuff += ::GetSystemMetrics(SM_CYMENU);
687 		::SetWindowLongPtr(HwndOf(wContent),
688 				   GWL_EXSTYLE, 0);
689 		::SetWindowPos(MainHWND(), HWND_TOP,
690 			       -::GetSystemMetrics(SM_CXSIZEFRAME),
691 			       -topStuff,
692 			       ::GetSystemMetrics(SM_CXSCREEN) + 2 * ::GetSystemMetrics(SM_CXSIZEFRAME),
693 			       ::GetSystemMetrics(SM_CYSCREEN) + topStuff + ::GetSystemMetrics(SM_CYSIZEFRAME),
694 			       0);
695 	} else {
696 		::ShowWindow(wTaskBar, SW_SHOW);
697 		if (wStartButton)
698 			::ShowWindow(wStartButton, SW_SHOW);
699 		::SetWindowLongPtr(HwndOf(wContent),
700 				   GWL_EXSTYLE, WS_EX_CLIENTEDGE);
701 		if (winPlace.length) {
702 			::SystemParametersInfo(SPI_SETWORKAREA, 0, &rcWorkArea, 0);
703 			if (winPlace.showCmd == SW_SHOWMAXIMIZED) {
704 				::ShowWindow(MainHWND(), SW_RESTORE);
705 				::ShowWindow(MainHWND(), SW_SHOWMAXIMIZED);
706 			} else {
707 				::SetWindowPlacement(MainHWND(), &winPlace);
708 			}
709 		}
710 	}
711 	::SetForegroundWindow(MainHWND());
712 	CheckMenus();
713 }
714 
MainHWND()715 HWND SciTEWin::MainHWND() noexcept {
716 	return HwndOf(wSciTE);
717 }
718 
Command(WPARAM wParam,LPARAM lParam)719 void SciTEWin::Command(WPARAM wParam, LPARAM lParam) {
720 	const int cmdID = ControlIDOfWParam(wParam);
721 	if (wParam & 0x10000) {
722 		// From accelerator -> goes to focused pane.
723 		menuSource = 0;
724 	}
725 	if (reinterpret_cast<HWND>(lParam) == wToolBar.GetID()) {
726 		// From toolbar -> goes to focused pane.
727 		menuSource = 0;
728 	}
729 	if (!menuSource) {
730 		if (!wEditor.HasFocus() && !wOutput.HasFocus()) {
731 			HWND wWithFocus = ::GetFocus();
732 			const GUI::gui_string classNameFocus = ClassNameOfWindow(wWithFocus);
733 			if (classNameFocus == GUI_TEXT("Edit")) {
734 				switch (cmdID) {
735 				case IDM_UNDO:
736 					::SendMessage(wWithFocus, EM_UNDO, 0, 0);
737 					return;
738 				case IDM_CUT:
739 					::SendMessage(wWithFocus, WM_CUT, 0, 0);
740 					return;
741 				case IDM_COPY:
742 					::SendMessage(wWithFocus, WM_COPY, 0, 0);
743 					return;
744 				}
745 			}
746 		}
747 	}
748 
749 	switch (cmdID) {
750 
751 	case IDM_ACTIVATE:
752 		Activate(lParam);
753 		break;
754 
755 	case IDM_FINISHEDEXECUTE: {
756 			jobQueue.SetExecuting(false);
757 			if (needReadProperties)
758 				ReadProperties();
759 			CheckMenus();
760 			jobQueue.ClearJobs();
761 			CheckReload();
762 		}
763 		break;
764 
765 	case IDM_ONTOP:
766 		topMost = (topMost ? false : true);
767 		::SetWindowPos(MainHWND(), (topMost ? HWND_TOPMOST : HWND_NOTOPMOST), 0, 0, 0, 0, SWP_NOMOVE + SWP_NOSIZE);
768 		CheckAMenuItem(IDM_ONTOP, topMost);
769 		break;
770 
771 	case IDM_OPENFILESHERE:
772 		uniqueInstance.ToggleOpenFilesHere();
773 		break;
774 
775 	case IDM_FULLSCREEN:
776 		FullScreenToggle();
777 		break;
778 
779 	case IDC_TABCLOSE:
780 		CloseTab(static_cast<int>(lParam));
781 		break;
782 
783 	case IDC_SHIFTTAB:
784 		ShiftTab(LOWORD(lParam), HIWORD(lParam));
785 		break;
786 
787 	default:
788 		SciTEBase::MenuCommand(cmdID, menuSource);
789 	}
790 }
791 
792 // from ScintillaWin.cxx
CodePageFromCharSet(SA::CharacterSet characterSet,UINT documentCodePage)793 static UINT CodePageFromCharSet(SA::CharacterSet characterSet, UINT documentCodePage) noexcept {
794 	CHARSETINFO ci {};
795 	const BOOL bci = ::TranslateCharsetInfo(reinterpret_cast<DWORD *>(static_cast<uintptr_t>(characterSet)),
796 						&ci, TCI_SRCCHARSET);
797 
798 	UINT cp = (bci) ? ci.ciACP : documentCodePage;
799 
800 	CPINFO cpi {};
801 	if (!::IsValidCodePage(cp) && !::GetCPInfo(cp, &cpi))
802 		cp = CP_ACP;
803 
804 	return cp;
805 }
806 
OutputAppendEncodedStringSynchronised(const GUI::gui_string & s,int codePageDocument)807 void SciTEWin::OutputAppendEncodedStringSynchronised(const GUI::gui_string &s, int codePageDocument) {
808 	std::string sMulti = StringEncode(s, codePageDocument);
809 	OutputAppendStringSynchronised(sMulti.c_str());
810 }
811 
CommandWorker()812 CommandWorker::CommandWorker() noexcept : pSciTE(nullptr) {
813 	Initialise(true);
814 }
815 
Initialise(bool resetToStart)816 void CommandWorker::Initialise(bool resetToStart) noexcept {
817 	if (resetToStart)
818 		icmd = 0;
819 	originalEnd = 0;
820 	exitStatus = 0;
821 	flags = 0;
822 	seenOutput = false;
823 	outputScroll = 1;
824 }
825 
Execute()826 void CommandWorker::Execute() {
827 	pSciTE->ProcessExecute();
828 }
829 
ResetExecution()830 void SciTEWin::ResetExecution() {
831 	cmdWorker.Initialise(true);
832 	jobQueue.SetExecuting(false);
833 	if (needReadProperties)
834 		ReadProperties();
835 	CheckReload();
836 	CheckMenus();
837 	jobQueue.ClearJobs();
838 	::SendMessage(MainHWND(), WM_COMMAND, IDM_FINISHEDEXECUTE, 0);
839 }
840 
ExecuteNext()841 void SciTEWin::ExecuteNext() {
842 	cmdWorker.icmd++;
843 	if (cmdWorker.icmd < jobQueue.commandCurrent && cmdWorker.icmd < jobQueue.commandMax && cmdWorker.exitStatus == 0) {
844 		Execute();
845 	} else {
846 		ResetExecution();
847 	}
848 }
849 
850 /**
851  * Run a command with redirected input and output streams
852  * so the output can be put in a window.
853  * It is based upon several usenet posts and a knowledge base article.
854  * This is running in a separate thread to the user interface so should always
855  * use ScintillaWindow::Send rather than a one of the direct function calls.
856  */
ExecuteOne(const Job & jobToRun)857 DWORD SciTEWin::ExecuteOne(const Job &jobToRun) {
858 	DWORD exitcode = 0;
859 
860 	if (jobToRun.jobType == jobShell) {
861 		ShellExec(jobToRun.command, jobToRun.directory.AsUTF8().c_str());
862 		return exitcode;
863 	}
864 
865 	if (jobToRun.jobType == jobHelp) {
866 		ExecuteHelp(jobToRun.command.c_str());
867 		return exitcode;
868 	}
869 
870 	if (jobToRun.jobType == jobOtherHelp) {
871 		ExecuteOtherHelp(jobToRun.command.c_str());
872 		return exitcode;
873 	}
874 
875 	if (jobToRun.jobType == jobGrep) {
876 		// jobToRun.command is "(w|~)(c|~)(d|~)(b|~)\0files\0text"
877 		const char *grepCmd = jobToRun.command.c_str();
878 		if (*grepCmd) {
879 			GrepFlags gf = grepNone;
880 			if (*grepCmd == 'w')
881 				gf = static_cast<GrepFlags>(gf | grepWholeWord);
882 			grepCmd++;
883 			if (*grepCmd == 'c')
884 				gf = static_cast<GrepFlags>(gf | grepMatchCase);
885 			grepCmd++;
886 			if (*grepCmd == 'd')
887 				gf = static_cast<GrepFlags>(gf | grepDot);
888 			grepCmd++;
889 			if (*grepCmd == 'b')
890 				gf = static_cast<GrepFlags>(gf | grepBinary);
891 			const char *findFiles = grepCmd + 2;
892 			const char *findText = findFiles + strlen(findFiles) + 1;
893 			if (cmdWorker.outputScroll == 1)
894 				gf = static_cast<GrepFlags>(gf | grepScroll);
895 			SA::Position positionEnd = wOutput.Send(SCI_GETCURRENTPOS);
896 			InternalGrep(gf, jobToRun.directory.AsInternal(), GUI::StringFromUTF8(findFiles).c_str(), findText, positionEnd);
897 			if ((gf & grepScroll) && returnOutputToCommand)
898 				wOutput.Send(SCI_GOTOPOS, positionEnd);
899 		}
900 		return exitcode;
901 	}
902 
903 	UINT codePageOutput = static_cast<UINT>(wOutput.Send(SCI_GETCODEPAGE));
904 	if (codePageOutput != SA::CpUtf8) {
905 		codePageOutput = CodePageFromCharSet(characterSet, codePageOutput);
906 	}
907 
908 	SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
909 	OutputAppendStringSynchronised(">");
910 	OutputAppendEncodedStringSynchronised(GUI::StringFromUTF8(jobToRun.command), codePageOutput);
911 	OutputAppendStringSynchronised("\n");
912 
913 	HANDLE hPipeWrite {};
914 	HANDLE hPipeRead {};
915 	// Create pipe for output redirection
916 	// read handle, write handle, security attributes,  number of bytes reserved for pipe
917 	constexpr DWORD pipeBufferSize = 64 * 1024;
918 	::CreatePipe(&hPipeRead, &hPipeWrite, &sa, pipeBufferSize);
919 
920 	// Create pipe for input redirection. In this code, you do not
921 	// redirect the output of the child process, but you need a handle
922 	// to set the hStdInput field in the STARTUP_INFO struct. For safety,
923 	// you should not set the handles to an invalid handle.
924 
925 	hWriteSubProcess = {};
926 	subProcessGroupId = 0;
927 	HANDLE hRead2 {};
928 	// read handle, write handle, security attributes,  number of bytes reserved for pipe
929 	::CreatePipe(&hRead2, &hWriteSubProcess, &sa, pipeBufferSize);
930 
931 	::SetHandleInformation(hPipeRead, HANDLE_FLAG_INHERIT, 0);
932 	::SetHandleInformation(hWriteSubProcess, HANDLE_FLAG_INHERIT, 0);
933 
934 	// Make child process use hPipeWrite as standard out, and make
935 	// sure it does not show on screen.
936 	STARTUPINFOW si = {};
937 	si.cb = sizeof(STARTUPINFO);
938 	si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
939 	if (jobToRun.jobType == jobCLI)
940 		si.wShowWindow = SW_HIDE;
941 	else
942 		si.wShowWindow = SW_SHOW;
943 	si.hStdInput = hRead2;
944 	si.hStdOutput = hPipeWrite;
945 	si.hStdError = hPipeWrite;
946 
947 	FilePath startDirectory = jobToRun.directory.AbsolutePath();
948 
949 	PROCESS_INFORMATION pi = {};
950 
951 	// Make a mutable copy as the CreateProcess parameter is mutable
952 	const GUI::gui_string sCommand = GUI::StringFromUTF8(jobToRun.command);
953 	std::vector<wchar_t> vwcCommand(sCommand.c_str(), sCommand.c_str() + sCommand.length() + 1);
954 
955 	BOOL running = ::CreateProcessW(
956 			       nullptr,
957 			       &vwcCommand[0],
958 			       nullptr, nullptr,
959 			       TRUE, CREATE_NEW_PROCESS_GROUP,
960 			       nullptr,
961 			       startDirectory.IsSet() ?
962 			       startDirectory.AsInternal() : nullptr,
963 			       &si, &pi);
964 
965 	const DWORD errCode = ::GetLastError();
966 	// if jobCLI "System can't find" - try calling with command processor
967 	if ((!running) && (jobToRun.jobType == jobCLI) && (
968 				(errCode == ERROR_FILE_NOT_FOUND) || (errCode == ERROR_BAD_EXE_FORMAT))) {
969 
970 		std::string runComLine = "cmd.exe /c ";
971 		runComLine = runComLine.append(jobToRun.command);
972 
973 		const GUI::gui_string sRunComLine = GUI::StringFromUTF8(runComLine);
974 		std::vector<wchar_t> vwcRunComLine(sRunComLine.c_str(), sRunComLine.c_str() + sRunComLine.length() + 1);
975 
976 		running = ::CreateProcessW(
977 				  nullptr,
978 				  &vwcRunComLine[0],
979 				  nullptr, nullptr,
980 				  TRUE, CREATE_NEW_PROCESS_GROUP,
981 				  nullptr,
982 				  startDirectory.IsSet() ?
983 				  startDirectory.AsInternal() : nullptr,
984 				  &si, &pi);
985 	}
986 
987 	if (running) {
988 		subProcessGroupId = pi.dwProcessId;
989 
990 		bool cancelled = false;
991 
992 		std::string repSelBuf;
993 
994 		size_t totalBytesToWrite = 0;
995 		if (jobToRun.flags & jobHasInput) {
996 			totalBytesToWrite = jobToRun.input.length();
997 		}
998 
999 		if (totalBytesToWrite > 0 && !(jobToRun.flags & jobQuiet)) {
1000 			std::string input = jobToRun.input;
1001 			Substitute(input, "\n", "\n>> ");
1002 
1003 			OutputAppendStringSynchronised(">> ");
1004 			OutputAppendStringSynchronised(input.c_str());
1005 			OutputAppendStringSynchronised("\n");
1006 		}
1007 
1008 		unsigned writingPosition = 0;
1009 
1010 		int countPeeks = 0;
1011 		bool processDead = false;
1012 		while (running) {
1013 			if (writingPosition >= totalBytesToWrite) {
1014 				if (countPeeks > 10)
1015 					::Sleep(100L);
1016 				else if (countPeeks > 2)
1017 					::Sleep(10L);
1018 				countPeeks++;
1019 			}
1020 
1021 			// If we don't already know the process is dead,
1022 			// check now (before polling the output pipe)
1023 			if (!processDead && (WAIT_OBJECT_0 == ::WaitForSingleObject(pi.hProcess, 0))) {
1024 				processDead = true;
1025 			}
1026 
1027 			DWORD bytesRead = 0;
1028 			DWORD bytesAvail = 0;
1029 			std::vector<char> buffer(pipeBufferSize);
1030 
1031 			if (!::PeekNamedPipe(hPipeRead, &buffer[0],
1032 					     static_cast<DWORD>(buffer.size()), &bytesRead, &bytesAvail, NULL)) {
1033 				bytesAvail = 0;
1034 			}
1035 
1036 			if ((bytesAvail < 1000) && (hWriteSubProcess != INVALID_HANDLE_VALUE) && (writingPosition < totalBytesToWrite)) {
1037 				// There is input to transmit to the process.  Do it in small blocks, interleaved
1038 				// with reads, so that our hRead buffer will not be overrun with results.
1039 
1040 				size_t bytesToWrite;
1041 				const size_t eolPos = jobToRun.input.find("\n", writingPosition);
1042 				if (eolPos == std::string::npos) {
1043 					bytesToWrite = totalBytesToWrite - writingPosition;
1044 				} else {
1045 					bytesToWrite = eolPos + 1 - writingPosition;
1046 				}
1047 				if (bytesToWrite > 250) {
1048 					bytesToWrite = 250;
1049 				}
1050 
1051 				DWORD bytesWrote = 0;
1052 
1053 				const int bTest = ::WriteFile(hWriteSubProcess,
1054 							      jobToRun.input.c_str() + writingPosition,
1055 							      static_cast<DWORD>(bytesToWrite), &bytesWrote, NULL);
1056 
1057 				if (bTest) {
1058 					if ((writingPosition + bytesToWrite) / 1024 > writingPosition / 1024) {
1059 						// sleep occasionally, even when writing
1060 						::Sleep(100L);
1061 					}
1062 
1063 					writingPosition += bytesWrote;
1064 
1065 					if (writingPosition >= totalBytesToWrite) {
1066 						::CloseHandle(hWriteSubProcess);
1067 						hWriteSubProcess = INVALID_HANDLE_VALUE;
1068 					}
1069 
1070 				} else {
1071 					// Is this the right thing to do when writing to the pipe fails?
1072 					::CloseHandle(hWriteSubProcess);
1073 					hWriteSubProcess = INVALID_HANDLE_VALUE;
1074 					OutputAppendStringSynchronised("\n>Input pipe closed due to write failure.\n");
1075 				}
1076 
1077 			} else if (bytesAvail > 0) {
1078 				const int bTest = ::ReadFile(hPipeRead, &buffer[0],
1079 							     static_cast<DWORD>(buffer.size()), &bytesRead, NULL);
1080 
1081 				if (bTest && bytesRead) {
1082 
1083 					if (jobToRun.flags & jobRepSelMask) {
1084 						repSelBuf.append(&buffer[0], bytesRead);
1085 					}
1086 
1087 					if (!(jobToRun.flags & jobQuiet)) {
1088 						if (!cmdWorker.seenOutput) {
1089 							ShowOutputOnMainThread();
1090 							cmdWorker.seenOutput = true;
1091 						}
1092 						// Display the data
1093 						OutputAppendStringSynchronised(&buffer[0], bytesRead);
1094 					}
1095 
1096 					::UpdateWindow(MainHWND());
1097 				} else {
1098 					running = false;
1099 				}
1100 			} else {
1101 				// bytesAvail == 0, and if the process
1102 				// was already dead by the time we did
1103 				// PeekNamedPipe, there should not be
1104 				// any more data coming
1105 				if (processDead) {
1106 					running = false;
1107 				}
1108 			}
1109 
1110 			if (jobQueue.SetCancelFlag(false)) {
1111 				if (WAIT_OBJECT_0 != ::WaitForSingleObject(pi.hProcess, 500)) {
1112 					// We should use it only if the GUI process is stuck and
1113 					// don't answer to a normal termination command.
1114 					// This function is dangerous: dependent DLLs don't know the process
1115 					// is terminated, and memory isn't released.
1116 					OutputAppendStringSynchronised("\n>Process failed to respond; forcing abrupt termination...\n");
1117 					::TerminateProcess(pi.hProcess, 1);
1118 				}
1119 				running = false;
1120 				cancelled = true;
1121 			}
1122 		}
1123 
1124 		if (WAIT_OBJECT_0 != ::WaitForSingleObject(pi.hProcess, 1000)) {
1125 			OutputAppendStringSynchronised("\n>Process failed to respond; forcing abrupt termination...");
1126 			::TerminateProcess(pi.hProcess, 2);
1127 		}
1128 		::GetExitCodeProcess(pi.hProcess, &exitcode);
1129 		std::ostringstream stExitMessage;
1130 		stExitMessage << ">Exit code: " << exitcode;
1131 		if (jobQueue.TimeCommands()) {
1132 			stExitMessage << "    Time: ";
1133 			stExitMessage << std::setprecision(4) << cmdWorker.commandTime.Duration();
1134 		}
1135 		stExitMessage << "\n";
1136 		OutputAppendStringSynchronised(stExitMessage.str().c_str());
1137 
1138 		::CloseHandle(pi.hProcess);
1139 		::CloseHandle(pi.hThread);
1140 
1141 		if (!cancelled) {
1142 			bool doRepSel = false;
1143 			if (jobToRun.flags & jobRepSelYes)
1144 				doRepSel = true;
1145 			else if (jobToRun.flags & jobRepSelAuto)
1146 				doRepSel = (0 == exitcode);
1147 
1148 			if (doRepSel) {
1149 				const SA::Position cpMin = wEditor.Send(SCI_GETSELECTIONSTART);
1150 				wEditor.Send(SCI_REPLACESEL, 0, SptrFromString(repSelBuf.c_str()));
1151 				wEditor.Send(SCI_SETSEL, cpMin, cpMin+repSelBuf.length());
1152 			}
1153 		}
1154 
1155 		WarnUser(warnExecuteOK);
1156 
1157 	} else {
1158 		const DWORD nRet = ::GetLastError();
1159 		OutputAppendStringSynchronised(">");
1160 		OutputAppendEncodedStringSynchronised(GetErrorMessage(nRet), codePageOutput);
1161 		WarnUser(warnExecuteKO);
1162 	}
1163 	::CloseHandle(hPipeRead);
1164 	::CloseHandle(hPipeWrite);
1165 	::CloseHandle(hRead2);
1166 	::CloseHandle(hWriteSubProcess);
1167 	hWriteSubProcess = {};
1168 	subProcessGroupId = 0;
1169 	return exitcode;
1170 }
1171 
1172 /**
1173  * Run a command in the job queue, stopping if one fails.
1174  * This is running in a separate thread to the user interface so must be
1175  * careful when reading and writing shared state.
1176  */
ProcessExecute()1177 void SciTEWin::ProcessExecute() {
1178 	if (scrollOutput)
1179 		wOutput.Send(SCI_GOTOPOS, wOutput.Send(SCI_GETTEXTLENGTH));
1180 
1181 	cmdWorker.exitStatus = ExecuteOne(jobQueue.jobQueue[cmdWorker.icmd]);
1182 	if (jobQueue.isBuilding) {
1183 		// The build command is first command in a sequence so it is only built if
1184 		// that command succeeds not if a second returns after document is modified.
1185 		jobQueue.isBuilding = false;
1186 		if (cmdWorker.exitStatus == 0)
1187 			jobQueue.isBuilt = true;
1188 	}
1189 
1190 	// Move selection back to beginning of this run so that F4 will go
1191 	// to first error of this run.
1192 	// scroll and return only if output.scroll equals
1193 	// one in the properties file
1194 	if ((cmdWorker.outputScroll == 1) && returnOutputToCommand)
1195 		wOutput.Send(SCI_GOTOPOS, cmdWorker.originalEnd);
1196 	returnOutputToCommand = true;
1197 	PostOnMainThread(WORK_EXECUTE, &cmdWorker);
1198 }
1199 
ShellExec(const std::string & cmd,const char * dir)1200 void SciTEWin::ShellExec(const std::string &cmd, const char *dir) {
1201 	// guess if cmd is an executable, if this succeeds it can
1202 	// contain spaces without enclosing it with "
1203 	std::string cmdLower = cmd;
1204 	LowerCaseAZ(cmdLower);
1205 	const char *mycmdLowered = cmdLower.c_str();
1206 
1207 	const char *s = strstr(mycmdLowered, ".exe");
1208 	if (!s)
1209 		s = strstr(mycmdLowered, ".cmd");
1210 	if (!s)
1211 		s = strstr(mycmdLowered, ".bat");
1212 	if (!s)
1213 		s = strstr(mycmdLowered, ".com");
1214 	std::vector<char> cmdcopy(cmd.c_str(), cmd.c_str() + cmd.length() + 1);
1215 	char *mycmdcopy = &cmdcopy[0];
1216 	char *mycmd;
1217 	char *mycmdEnd = nullptr;
1218 	if (s && ((*(s + 4) == '\0') || (*(s + 4) == ' '))) {
1219 		ptrdiff_t len_mycmd = s - mycmdLowered + 4;
1220 		mycmd = mycmdcopy;
1221 		mycmdEnd = mycmdcopy + len_mycmd;
1222 	} else {
1223 		if (*mycmdcopy != '"') {
1224 			// get next space to separate cmd and parameters
1225 			mycmd = mycmdcopy;
1226 			mycmdEnd = strchr(mycmdcopy, ' ');
1227 		} else {
1228 			// the cmd is surrounded by ", so it can contain spaces, but we must
1229 			// strip the " for ShellExec
1230 			mycmd = mycmdcopy + 1;
1231 			char *sm = strchr(mycmdcopy + 1, '"');
1232 			if (sm) {
1233 				*sm = '\0';
1234 				mycmdEnd = sm + 1;
1235 			}
1236 		}
1237 	}
1238 
1239 	std::string myparams;
1240 	if (mycmdEnd && (*mycmdEnd != '\0')) {
1241 		*mycmdEnd = '\0';
1242 		// test for remaining params after cmd, they may be surrounded by " but
1243 		// we give them as-is to ShellExec
1244 		++mycmdEnd;
1245 		while (*mycmdEnd == ' ')
1246 			++mycmdEnd;
1247 
1248 		if (*mycmdEnd != '\0')
1249 			myparams = mycmdEnd;
1250 	}
1251 
1252 	GUI::gui_string sMycmd = GUI::StringFromUTF8(mycmd);
1253 	GUI::gui_string sMyparams = GUI::StringFromUTF8(myparams);
1254 	GUI::gui_string sDir = GUI::StringFromUTF8(dir);
1255 
1256 	SHELLEXECUTEINFO exec {};
1257 	exec.cbSize = sizeof(exec);
1258 	exec.fMask = SEE_MASK_FLAG_NO_UI; // own msg box on return
1259 	exec.hwnd = MainHWND();
1260 	exec.lpVerb = L"open";  // better for executables to use "open" instead of NULL
1261 	exec.lpFile = sMycmd.c_str();   // file to open
1262 	exec.lpParameters = sMyparams.c_str(); // parameters
1263 	exec.lpDirectory = sDir.c_str(); // launch directory
1264 	exec.nShow = SW_SHOWNORMAL; //default show cmd
1265 
1266 	if (::ShellExecuteEx(&exec)) {
1267 		// it worked!
1268 		return;
1269 	}
1270 	const DWORD rc = GetLastError();
1271 
1272 	std::string errormsg("Error while launching:\n\"");
1273 	errormsg += mycmdcopy;
1274 	if (myparams.length()) {
1275 		errormsg += "\" with Params:\n\"";
1276 		errormsg += myparams;
1277 	}
1278 	errormsg += "\"\n";
1279 	GUI::gui_string sErrorMsg = GUI::StringFromUTF8(errormsg) + GetErrorMessage(rc);
1280 	WindowMessageBox(wSciTE, sErrorMsg, mbsOK);
1281 }
1282 
Execute()1283 void SciTEWin::Execute() {
1284 	if (buffers.SavingInBackground())
1285 		// May be saving file that should be used by command so wait until all saved
1286 		return;
1287 
1288 	SciTEBase::Execute();
1289 	if (!jobQueue.HasCommandToRun())
1290 		// No commands to execute - possibly cancelled in SciTEBase::Execute
1291 		return;
1292 
1293 	cmdWorker.Initialise(false);
1294 	cmdWorker.outputScroll = props.GetInt("output.scroll", 1);
1295 	cmdWorker.originalEnd = wOutput.Length();
1296 	cmdWorker.commandTime.Duration(true);
1297 	cmdWorker.flags = jobQueue.jobQueue[cmdWorker.icmd].flags;
1298 	if (scrollOutput)
1299 		wOutput.GotoPos(wOutput.Length());
1300 
1301 	if (jobQueue.jobQueue[cmdWorker.icmd].jobType == jobExtension) {
1302 		// Execute extensions synchronously
1303 		if (jobQueue.jobQueue[cmdWorker.icmd].flags & jobGroupUndo)
1304 			wEditor.BeginUndoAction();
1305 
1306 		if (extender)
1307 			extender->OnExecute(jobQueue.jobQueue[cmdWorker.icmd].command.c_str());
1308 
1309 		if (jobQueue.jobQueue[cmdWorker.icmd].flags & jobGroupUndo)
1310 			wEditor.EndUndoAction();
1311 
1312 		ExecuteNext();
1313 	} else {
1314 		// Execute other jobs asynchronously on a new thread
1315 		PerformOnNewThread(&cmdWorker);
1316 	}
1317 }
1318 
StopExecute()1319 void SciTEWin::StopExecute() {
1320 	if (hWriteSubProcess && (hWriteSubProcess != INVALID_HANDLE_VALUE)) {
1321 		const char stop[] = "\032";
1322 		DWORD bytesWrote = 0;
1323 		::WriteFile(hWriteSubProcess, stop, static_cast<DWORD>(strlen(stop)), &bytesWrote, nullptr);
1324 		Sleep(500L);
1325 	}
1326 
1327 #ifdef USE_CONSOLE_EVENT
1328 	if (subProcessGroupId) {
1329 		// this also doesn't work
1330 		OutputAppendStringSynchronised("\n>Attempting to cancel process...");
1331 
1332 		if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, subProcessGroupId)) {
1333 			LONG errCode = GetLastError();
1334 			OutputAppendStringSynchronised("\n>BREAK Failed ");
1335 			std::string sError = StdStringFromInteger(errCode);
1336 			OutputAppendStringSynchronised(sError.c_str());
1337 			OutputAppendStringSynchronised("\n");
1338 		}
1339 		Sleep(100L);
1340 	}
1341 #endif
1342 
1343 	jobQueue.SetCancelFlag(true);
1344 }
1345 
AddCommand(const std::string & cmd,const std::string & dir,JobSubsystem jobType,const std::string & input,int flags)1346 void SciTEWin::AddCommand(const std::string &cmd, const std::string &dir, JobSubsystem jobType, const std::string &input, int flags) {
1347 	if (cmd.length()) {
1348 		if ((jobType == jobShell) && ((flags & jobForceQueue) == 0)) {
1349 			std::string pCmd = cmd;
1350 			parameterisedCommand = "";
1351 			if (pCmd[0] == '*') {
1352 				pCmd.erase(0, 1);
1353 				parameterisedCommand = pCmd;
1354 				if (!ParametersDialog(true)) {
1355 					return;
1356 				}
1357 			} else {
1358 				ParamGrab();
1359 			}
1360 			pCmd = props.Expand(pCmd);
1361 			ShellExec(pCmd, dir.c_str());
1362 		} else {
1363 			SciTEBase::AddCommand(cmd, dir, jobType, input, flags);
1364 		}
1365 	}
1366 }
1367 
PostOnMainThread(int cmd,Worker * pWorker)1368 void SciTEWin::PostOnMainThread(int cmd, Worker *pWorker) {
1369 	::PostMessage(HwndOf(wSciTE), SCITE_WORKER, cmd, reinterpret_cast<LPARAM>(pWorker));
1370 }
1371 
WorkerCommand(int cmd,Worker * pWorker)1372 void SciTEWin::WorkerCommand(int cmd, Worker *pWorker) {
1373 	if (cmd < WORK_PLATFORM) {
1374 		SciTEBase::WorkerCommand(cmd, pWorker);
1375 	} else {
1376 		if (cmd == WORK_EXECUTE) {
1377 			// Move to next command
1378 			ExecuteNext();
1379 		}
1380 	}
1381 }
1382 
QuitProgram()1383 void SciTEWin::QuitProgram() {
1384 	quitting = false;
1385 	if (SaveIfUnsureAll() != saveCancelled) {
1386 		if (fullScreen)	// Ensure tray visible on exit
1387 			FullScreenToggle();
1388 		quitting = true;
1389 		// If ongoing saves, wait for them to complete.
1390 		if (!buffers.SavingInBackground()) {
1391 			::PostQuitMessage(0);
1392 			wSciTE.Destroy();
1393 		}
1394 	}
1395 }
1396 
RestorePosition()1397 void SciTEWin::RestorePosition() {
1398 	const int left = propsSession.GetInt("position.left", CW_USEDEFAULT);
1399 	const int top = propsSession.GetInt("position.top", CW_USEDEFAULT);
1400 	const int width = propsSession.GetInt("position.width", CW_USEDEFAULT);
1401 	const int height = propsSession.GetInt("position.height", CW_USEDEFAULT);
1402 	cmdShow = propsSession.GetInt("position.maximize", 0) ? SW_MAXIMIZE : 0;
1403 
1404 	constexpr int defaultValue = CW_USEDEFAULT;
1405 	if (left != defaultValue &&
1406 			top != defaultValue &&
1407 			width != defaultValue &&
1408 			height != defaultValue) {
1409 		winPlace.length = sizeof(winPlace);
1410 		winPlace.rcNormalPosition.left = left;
1411 		winPlace.rcNormalPosition.right = left + width;
1412 		winPlace.rcNormalPosition.top = top;
1413 		winPlace.rcNormalPosition.bottom = top + height;
1414 		::SetWindowPlacement(MainHWND(), &winPlace);
1415 	}
1416 }
1417 
CreateUI()1418 void SciTEWin::CreateUI() {
1419 	CreateBuffers();
1420 
1421 	int left = props.GetInt("position.left", CW_USEDEFAULT);
1422 	const int top = props.GetInt("position.top", CW_USEDEFAULT);
1423 	int width = props.GetInt("position.width", CW_USEDEFAULT);
1424 	int height = props.GetInt("position.height", CW_USEDEFAULT);
1425 	cmdShow = props.GetInt("position.maximize", 0) ? SW_MAXIMIZE : 0;
1426 	if (width == -1 || height == -1) {
1427 		cmdShow = SW_MAXIMIZE;
1428 		width = CW_USEDEFAULT;
1429 		height = CW_USEDEFAULT;
1430 	}
1431 
1432 	if (props.GetInt("position.tile") && ::FindWindow(TEXT("SciTEWindow"), nullptr) &&
1433 			(left != static_cast<int>(CW_USEDEFAULT))) {
1434 		left += width;
1435 	}
1436 	// Pass 'this' pointer in lpParam of CreateWindow().
1437 	wSciTE = ::CreateWindowEx(
1438 			 0,
1439 			 className,
1440 			 windowName.c_str(),
1441 			 WS_CAPTION | WS_SYSMENU | WS_THICKFRAME |
1442 			 WS_MINIMIZEBOX | WS_MAXIMIZEBOX |
1443 			 WS_CLIPCHILDREN,
1444 			 left, top, width, height,
1445 			 NULL,
1446 			 NULL,
1447 			 hInstance,
1448 			 this);
1449 	if (!wSciTE.Created())
1450 		exit(FALSE);
1451 
1452 	if (props.GetInt("save.position"))
1453 		RestorePosition();
1454 
1455 	LocaliseMenus();
1456 	std::string pageSetup = props.GetString("print.margins");
1457 	char val[32] = "";
1458 	const char *ps = pageSetup.c_str();
1459 	const char *next = GetNextPropItem(ps, val, 32);
1460 	pagesetupMargin.left = atol(val);
1461 	next = GetNextPropItem(next, val, 32);
1462 	pagesetupMargin.right = atol(val);
1463 	next = GetNextPropItem(next, val, 32);
1464 	pagesetupMargin.top = atol(val);
1465 	GetNextPropItem(next, val, 32);
1466 	pagesetupMargin.bottom = atol(val);
1467 
1468 	UIAvailable();
1469 }
1470 
IsSpaceOrTab(GUI::gui_char ch)1471 static bool IsSpaceOrTab(GUI::gui_char ch) noexcept {
1472 	return (ch == ' ') || (ch == '\t');
1473 }
1474 
1475 /**
1476  * Break up the command line into individual arguments and strip double quotes
1477  * from each argument.
1478  * @return A string with each argument separated by '\n'.
1479  */
ProcessArgs(const GUI::gui_char * cmdLine)1480 GUI::gui_string SciTEWin::ProcessArgs(const GUI::gui_char *cmdLine) {
1481 	GUI::gui_string args;
1482 	const GUI::gui_char *startArg = cmdLine;
1483 	while (*startArg) {
1484 		while (IsSpaceOrTab(*startArg)) {
1485 			startArg++;
1486 		}
1487 		const GUI::gui_char *endArg = startArg;
1488 		if (*startArg == '"') {	// Opening double-quote
1489 			startArg++;
1490 			endArg = startArg;
1491 			while (*endArg && *endArg != '\"') {
1492 				endArg++;
1493 			}
1494 		} else {	// No double-quote, end of argument on first space
1495 			while (*endArg && !IsSpaceOrTab(*endArg)) {
1496 				endArg++;
1497 			}
1498 		}
1499 		GUI::gui_string arg(startArg, 0, endArg - startArg);
1500 		if (args.size() > 0)
1501 			args += GUI_TEXT("\n");
1502 		args += arg;
1503 		startArg = endArg;	// On a space or a double-quote, or on the end of the command line
1504 		if (*startArg == '"') {	// Closing double-quote
1505 			startArg++;	// Consume the double-quote
1506 		}
1507 		while (IsSpaceOrTab(*startArg)) {
1508 			// Consume spaces between arguments
1509 			startArg++;
1510 		}
1511 	}
1512 
1513 	return args;
1514 }
1515 
1516 /**
1517  * Process the command line, check for other instance wanting to open files,
1518  * create the SciTE window, perform batch processing (print) or transmit command line
1519  * to other instance and exit or just show the window and open files.
1520  */
Run(const GUI::gui_char * cmdLine)1521 void SciTEWin::Run(const GUI::gui_char *cmdLine) {
1522 	// Load the default session file
1523 	if (props.GetInt("save.session") || props.GetInt("save.position") || props.GetInt("save.recent")) {
1524 		LoadSessionFile(GUI_TEXT(""));
1525 	}
1526 
1527 	// Break up the command line into individual arguments
1528 	GUI::gui_string args = ProcessArgs(cmdLine);
1529 	// Read the command line parameters:
1530 	// In case the check.if.already.open property has been set or reset on the command line,
1531 	// we still get a last chance to force checking or to open a separate instance;
1532 	// Check if the user just want to print the file(s).
1533 	// Don't process files yet.
1534 	const bool bBatchProcessing = ProcessCommandLine(args, 0);
1535 
1536 	// No need to check for other instances when doing a batch job:
1537 	// perform some tasks and exit immediately.
1538 	if (!bBatchProcessing && props.GetInt("check.if.already.open") != 0) {
1539 		uniqueInstance.CheckOtherInstance();
1540 	}
1541 
1542 	// We create the window, so it can be found by EnumWindows below,
1543 	// and the Scintilla control is thus created, allowing to print the file(s).
1544 	// We don't show it yet, so if it is destroyed (duplicate instance), it will
1545 	// not flash on the taskbar or on the display.
1546 	CreateUI();
1547 
1548 	if (bBatchProcessing) {
1549 		// Reprocess the command line and read the files
1550 		ProcessCommandLine(args, 1);
1551 		Print(false);	// Don't ask user for print parameters
1552 		// Done, we exit the program
1553 		::PostQuitMessage(0);
1554 		wSciTE.Destroy();
1555 		return;
1556 	}
1557 
1558 	if (props.GetInt("check.if.already.open") != 0 && uniqueInstance.FindOtherInstance()) {
1559 		uniqueInstance.SendCommands(GUI::UTF8FromString(cmdLine).c_str());
1560 
1561 		// Kill itself, leaving room to the previous instance
1562 		::PostQuitMessage(0);
1563 		wSciTE.Destroy();
1564 		return;	// Don't do anything else
1565 	}
1566 
1567 	// OK, the instance will be displayed
1568 	SizeSubWindows();
1569 	wSciTE.Show();
1570 	if (cmdShow) {	// assume SW_MAXIMIZE only
1571 		::ShowWindow(MainHWND(), cmdShow);
1572 	}
1573 
1574 	// Open all files given on command line.
1575 	// The filenames containing spaces must be enquoted.
1576 	// In case of not using buffers they get closed immediately except
1577 	// the last one, but they move to the MRU file list
1578 	ProcessCommandLine(args, 1);
1579 	Redraw();
1580 }
1581 
1582 /**
1583  * Draw the split bar.
1584  */
Paint(HDC hDC,GUI::Rectangle)1585 void ContentWin::Paint(HDC hDC, GUI::Rectangle) {
1586 	const GUI::Rectangle rcInternal = GetClientPosition();
1587 
1588 	const int heightClient = rcInternal.Height();
1589 	const int widthClient = rcInternal.Width();
1590 
1591 	const int heightEditor = heightClient - pSciTEWin->heightOutput - pSciTEWin->heightBar;
1592 	const int yBorder = heightEditor;
1593 	const int xBorder = widthClient - pSciTEWin->heightOutput - pSciTEWin->heightBar;
1594 	for (int i = 0; i < pSciTEWin->heightBar; i++) {
1595 		int colourIndex = COLOR_3DFACE;
1596 		if (pSciTEWin->flatterUI) {
1597 			if (i == 0 || i == pSciTEWin->heightBar - 1)
1598 				colourIndex = COLOR_3DFACE;
1599 			else
1600 				colourIndex = COLOR_WINDOW;
1601 		} else {
1602 			if (i == 1)
1603 				colourIndex = COLOR_3DHIGHLIGHT;
1604 			else if (i == pSciTEWin->heightBar - 2)
1605 				colourIndex = COLOR_3DSHADOW;
1606 			else if (i == pSciTEWin->heightBar - 1)
1607 				colourIndex = COLOR_3DDKSHADOW;
1608 			else
1609 				colourIndex = COLOR_3DFACE;
1610 		}
1611 		HPEN pen = ::CreatePen(0, 1, ::GetSysColor(colourIndex));
1612 		HPEN penOld = SelectPen(hDC, pen);
1613 		if (pSciTEWin->splitVertical) {
1614 			::MoveToEx(hDC, xBorder + i, 0, nullptr);
1615 			::LineTo(hDC, xBorder + i, heightClient);
1616 		} else {
1617 			::MoveToEx(hDC, 0, yBorder + i, nullptr);
1618 			::LineTo(hDC, widthClient, yBorder + i);
1619 		}
1620 		SelectPen(hDC, penOld);
1621 		DeletePen(pen);
1622 	}
1623 }
1624 
AboutDialog()1625 void SciTEWin::AboutDialog() {
1626 #ifdef STATIC_BUILD
1627 	AboutDialogWithBuild(1);
1628 #else
1629 	AboutDialogWithBuild(0);
1630 #endif
1631 }
1632 
1633 /**
1634  * Open files dropped on the SciTE window.
1635  */
DropFiles(HDROP hdrop)1636 void SciTEWin::DropFiles(HDROP hdrop) {
1637 	// If drag'n'drop inside the SciTE window but outside
1638 	// Scintilla, hdrop is null, and an exception is generated!
1639 	if (hdrop) {
1640 		const bool tempFilesSyncLoad = props.GetInt("temp.files.sync.load") != 0;
1641 		GUI::gui_char tempDir[MAX_PATH];
1642 		const DWORD tempDirLen = ::GetTempPath(MAX_PATH, tempDir);
1643 		bool isTempFile = false;
1644 		const int filesDropped = ::DragQueryFile(hdrop, 0xffffffff, nullptr, 0);
1645 		// Append paths to dropFilesQueue, to finish drag operation soon
1646 		for (int i = 0; i < filesDropped; ++i) {
1647 			GUI::gui_char pathDropped[MAX_PATH];
1648 			::DragQueryFileW(hdrop, i, pathDropped,
1649 					 static_cast<UINT>(std::size(pathDropped)));
1650 			// Only do this for the first file in the drop op
1651 			// as all are coming from the same drag location
1652 			if (i == 0 && tempFilesSyncLoad) {
1653 				// check if file's parent dir is temp
1654 				if (::wcsncmp(tempDir, pathDropped, tempDirLen) == 0) {
1655 					isTempFile = true;
1656 				}
1657 			}
1658 			if (isTempFile) {
1659 				if (!Open(pathDropped, ofSynchronous)) {
1660 					break;
1661 				}
1662 			} else {
1663 				dropFilesQueue.push_back(pathDropped);
1664 			}
1665 		}
1666 		::DragFinish(hdrop);
1667 		// Put SciTE to forefront
1668 		// May not work for Win2k, but OK for lower versions
1669 		// Note: how to drop a file to an iconic window?
1670 		// Actually, it is the Send To command that generates a drop.
1671 		if (::IsIconic(MainHWND())) {
1672 			::ShowWindow(MainHWND(), SW_RESTORE);
1673 		}
1674 		::SetForegroundWindow(MainHWND());
1675 		// Post message to ourself for opening the files so we can finish the drop message and
1676 		// the drop source will respond when open operation takes long time (opening big files...)
1677 		if (!dropFilesQueue.empty()) {
1678 			::PostMessage(MainHWND(), SCITE_DROP, 0, 0);
1679 		}
1680 	}
1681 }
1682 
1683 /**
1684  * Handle simple wild-card file patterns and directory requests.
1685  */
PreOpenCheck(const GUI::gui_char * arg)1686 bool SciTEWin::PreOpenCheck(const GUI::gui_char *arg) {
1687 	bool isHandled = false;
1688 	HANDLE hFFile {};
1689 	WIN32_FIND_DATA ffile {};
1690 	const DWORD fileattributes = ::GetFileAttributes(arg);
1691 	int nbuffers = props.GetInt("buffers");
1692 	FilePath fpArg(arg);
1693 
1694 	if (fileattributes != INVALID_FILE_ATTRIBUTES) {	// arg is an existing directory or filename
1695 		// if the command line argument is a directory, use OpenDialog()
1696 		if (fileattributes & FILE_ATTRIBUTE_DIRECTORY) {
1697 			OpenDialog(fpArg, GUI::StringFromUTF8(props.GetExpandedString("open.filter")).c_str());
1698 			isHandled = true;
1699 		}
1700 	} else if (nbuffers > 1 && (hFFile = ::FindFirstFile(arg, &ffile)) != INVALID_HANDLE_VALUE) {
1701 		// If several buffers is accepted and the arg is a filename pattern matching at least an existing file
1702 		isHandled = true;
1703 		FilePath fpDir = fpArg.Directory();
1704 
1705 		do {
1706 			if (!(ffile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {	// Skip directories
1707 				Open(FilePath(fpDir, ffile.cFileName));
1708 				--nbuffers;
1709 			}
1710 		} while (nbuffers > 0 && ::FindNextFile(hFFile, &ffile));
1711 		::FindClose(hFFile);
1712 	} else {
1713 
1714 		// if the filename is only an extension, open the dialog box with it as the extension filter
1715 		if (!fpArg.BaseName().IsSet()) {
1716 			isHandled = true;
1717 			FilePath fpDir = fpArg.Directory();
1718 			if (!fpDir.IsSet())
1719 				fpDir = FilePath(GUI_TEXT("."));
1720 			FilePath fpName = fpArg.Name();
1721 			GUI::gui_string wildcard(GUI_TEXT("*"));
1722 			wildcard += fpName.AsInternal();
1723 			wildcard += GUI_TEXT("|*");
1724 			wildcard += fpName.AsInternal();
1725 
1726 			OpenDialog(fpDir, wildcard.c_str());
1727 		} else if (!fpArg.Extension().IsSet()) {
1728 			// if the filename has no extension, try to match a file with list of standard extensions
1729 			std::string extensions = props.GetExpandedString("source.default.extensions");
1730 			if (extensions.length()) {
1731 				std::replace(extensions.begin(), extensions.end(), '|', '\0');
1732 				size_t start = 0;
1733 				while (start < extensions.length()) {
1734 					GUI::gui_string filterName = GUI::StringFromUTF8(extensions.c_str() + start);
1735 					GUI::gui_string nameWithExtension = fpArg.AsInternal();
1736 					nameWithExtension += filterName;
1737 					if (::GetFileAttributes(nameWithExtension.c_str()) != INVALID_FILE_ATTRIBUTES) {
1738 						isHandled = true;
1739 						Open(nameWithExtension);
1740 						break;	// Found!
1741 					} else {
1742 						// Next extension
1743 						start += strlen(extensions.c_str() + start) + 1;
1744 					}
1745 				}
1746 			}
1747 		}
1748 	}
1749 
1750 	return isHandled;
1751 }
1752 
1753 /* return true if stdin is blocked:
1754 	- stdin is the console (isatty() == 1)
1755 	- a valid handle for stdin cannot be generated
1756 	- the handle appears to be the console - this may be a duplicate of using isatty() == 1
1757 	- the pipe cannot be peeked, which appears to be from command lines such as "scite <file.txt"
1758 	otherwise it is unblocked
1759 */
IsStdinBlocked()1760 bool SciTEWin::IsStdinBlocked() {
1761 	DWORD unreadMessages = 0;
1762 	INPUT_RECORD irec[1] = {};
1763 	char bytebuffer = '\0';
1764 	HANDLE hStdIn = ::GetStdHandle(STD_INPUT_HANDLE);
1765 	if (hStdIn == INVALID_HANDLE_VALUE) {
1766 		/* an invalid handle, assume that stdin is blocked by falling to bottom */;
1767 	} else if (::PeekConsoleInput(hStdIn, irec, 1, &unreadMessages) != 0) {
1768 		/* it is the console, assume that stdin is blocked by falling to bottom */;
1769 	} else if (::GetLastError() == ERROR_INVALID_HANDLE) {
1770 		for (int n = 0; n < 4; n++) {
1771 			/*	if this fails, it is either
1772 				- a busy pipe "scite \*.,cxx /s /b | s -@",
1773 				- another type of pipe "scite - <file", or
1774 				- a blocked pipe "findstring nothing | scite -"
1775 				in any case, retry in a short bit
1776 			*/
1777 			if (::PeekNamedPipe(hStdIn, &bytebuffer, sizeof(bytebuffer), nullptr, nullptr, &unreadMessages) != 0) {
1778 				if (unreadMessages != 0) {
1779 					return false; /* is a pipe and it is not blocked */
1780 				}
1781 			}
1782 			::Sleep(2500);
1783 		}
1784 	}
1785 	return true;
1786 }
1787 
MinimizeToTray()1788 void SciTEWin::MinimizeToTray() {
1789 	NOTIFYICONDATA nid {};
1790 	nid.cbSize = sizeof(nid);
1791 	nid.hWnd = MainHWND();
1792 	nid.uID = 1;
1793 	nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1794 	nid.uCallbackMessage = SCITE_TRAY;
1795 	nid.hIcon = static_cast<HICON>(
1796 			    ::LoadImage(hInstance, TEXT("SCITE"), IMAGE_ICON, 16, 16, LR_DEFAULTSIZE));
1797 	StringCopy(nid.szTip, TEXT("SciTE"));
1798 	::ShowWindow(MainHWND(), SW_MINIMIZE);
1799 	if (::Shell_NotifyIcon(NIM_ADD, &nid)) {
1800 		::ShowWindow(MainHWND(), SW_HIDE);
1801 	}
1802 }
1803 
RestoreFromTray()1804 void SciTEWin::RestoreFromTray() {
1805 	NOTIFYICONDATA nid {};
1806 	nid.cbSize = sizeof(nid);
1807 	nid.hWnd = MainHWND();
1808 	nid.uID = 1;
1809 	::ShowWindow(MainHWND(), SW_SHOW);
1810 	::Sleep(100);
1811 	::Shell_NotifyIcon(NIM_DELETE, &nid);
1812 }
1813 
SettingChanged(WPARAM wParam,LPARAM lParam)1814 void SciTEWin::SettingChanged(WPARAM wParam, LPARAM lParam) {
1815 	if (lParam) {
1816 		const GUI::gui_string_view sv(reinterpret_cast<const wchar_t *>(lParam));
1817 		if (sv == L"ImmersiveColorSet") {
1818 			CheckAppearanceChanged();
1819 		}
1820 	}
1821 	wEditor.Send(WM_SETTINGCHANGE, wParam, lParam);
1822 	wOutput.Send(WM_SETTINGCHANGE, wParam, lParam);
1823 }
1824 
SysColourChanged(WPARAM wParam,LPARAM lParam)1825 void SciTEWin::SysColourChanged(WPARAM wParam, LPARAM lParam) {
1826 	CheckAppearanceChanged();
1827 	wEditor.Send(WM_SYSCOLORCHANGE, wParam, lParam);
1828 	wOutput.Send(WM_SYSCOLORCHANGE, wParam, lParam);
1829 }
1830 
ScaleChanged(WPARAM wParam,LPARAM lParam)1831 void SciTEWin::ScaleChanged(WPARAM wParam, LPARAM lParam) {
1832 	const int scale = LOWORD(wParam) * 100 / 96;
1833 	if (SetScaleFactor(scale)) {
1834 		wEditor.Send(WM_DPICHANGED, wParam, lParam);
1835 		wOutput.Send(WM_DPICHANGED, wParam, lParam);
1836 		ReloadProperties();
1837 		const RECT *rect = reinterpret_cast<const RECT *>(lParam);
1838 		::SetWindowPos(MainHWND(), NULL, rect->left, rect->top, rect->right - rect->left, rect->bottom - rect->top,
1839 			SWP_NOZORDER | SWP_NOACTIVATE);
1840 
1841 		if (!fnSystemParametersInfoForDpi) {
1842 			fnSystemParametersInfoForDpi = DLLFunction<SystemParametersInfoForDpiSig>(
1843 				L"user32.dll", "SystemParametersInfoForDpi");
1844 		}
1845 
1846 		if (fnSystemParametersInfoForDpi) {
1847 			LOGFONTW lfIconTitle{};
1848 			if (fnSystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitle),
1849 				&lfIconTitle, FALSE, LOWORD(wParam))) {
1850 				const HFONT fontTabsPrevious = fontTabs;
1851 				fontTabs = ::CreateFontIndirectW(&lfIconTitle);
1852 				SetWindowFont(HwndOf(wTabBar), fontTabs, 0);
1853 				::DeleteObject(fontTabsPrevious);
1854 				SizeSubWindows();
1855 			}
1856 		}
1857 	}
1858 }
1859 
KeyMatch(const std::string & sKey,int keyval,int modifiers)1860 inline bool KeyMatch(const std::string &sKey, int keyval, int modifiers) {
1861 	return SciTEKeys::MatchKeyCode(
1862 		       SciTEKeys::ParseKeyCode(sKey.c_str()), keyval, modifiers);
1863 }
1864 
KeyDown(WPARAM wParam)1865 LRESULT SciTEWin::KeyDown(WPARAM wParam) {
1866 	// Look through lexer menu
1867 	const SA::KeyMod modifiers =
1868 		(IsKeyDown(VK_SHIFT) ? SA::KeyMod::Shift : SA::KeyMod::Norm) |
1869 		(IsKeyDown(VK_CONTROL) ? SA::KeyMod::Ctrl : SA::KeyMod::Norm) |
1870 		(IsKeyDown(VK_MENU) ? SA::KeyMod::Alt : SA::KeyMod::Norm);
1871 
1872 	const int keyVal = static_cast<int>(wParam);
1873 	const int modifierAsInt = static_cast<int>(modifiers);
1874 
1875 	if (extender && extender->OnKey(keyVal, modifierAsInt))
1876 		return 1l;
1877 
1878 	for (unsigned int j = 0; j < languageMenu.size(); j++) {
1879 		if (KeyMatch(languageMenu[j].menuKey, keyVal, modifierAsInt)) {
1880 			SciTEBase::MenuCommand(IDM_LANGUAGE + j);
1881 			return 1l;
1882 		}
1883 	}
1884 
1885 	// loop through the Tools menu's active commands.
1886 	HMENU hMenu = ::GetMenu(MainHWND());
1887 	HMENU hToolsMenu = ::GetSubMenu(hMenu, menuTools);
1888 	for (int tool = 0; tool < toolMax; ++tool) {
1889 		MENUITEMINFO mii;
1890 		mii.cbSize = sizeof(MENUITEMINFO);
1891 		mii.fMask = MIIM_DATA;
1892 		if (::GetMenuItemInfo(hToolsMenu, IDM_TOOLS+tool, FALSE, &mii) && mii.dwItemData) {
1893 			if (SciTEKeys::MatchKeyCode(static_cast<long>(mii.dwItemData), keyVal, modifierAsInt)) {
1894 				SciTEBase::MenuCommand(IDM_TOOLS+tool);
1895 				return 1l;
1896 			}
1897 		}
1898 	}
1899 
1900 	// loop through the keyboard short cuts defined by user.. if found
1901 	// exec it the command defined
1902 	for (const ShortcutItem &scut : shortCutItemList) {
1903 		if (KeyMatch(scut.menuKey, keyVal, modifierAsInt)) {
1904 			const int commandNum = SciTEBase::GetMenuCommandAsInt(scut.menuCommand.c_str());
1905 			if (commandNum != -1) {
1906 				// its possible that the command is for scintilla directly
1907 				// all scintilla commands are larger then 2000
1908 				if (commandNum < 2000) {
1909 					SciTEBase::MenuCommand(commandNum);
1910 				} else {
1911 					PaneFocused().Call(static_cast<SA::Message>(commandNum));
1912 				}
1913 				return 1l;
1914 			}
1915 		}
1916 	}
1917 
1918 	return 0;
1919 }
1920 
KeyUp(WPARAM wParam)1921 LRESULT SciTEWin::KeyUp(WPARAM wParam) {
1922 	if (wParam == VK_CONTROL) {
1923 		EndStackedTabbing();
1924 	}
1925 	return 0;
1926 }
1927 
AddToPopUp(const char * label,int cmd,bool enabled)1928 void SciTEWin::AddToPopUp(const char *label, int cmd, bool enabled) {
1929 	GUI::gui_string localised = localiser.Text(label);
1930 	HMENU menu = static_cast<HMENU>(popup.GetID());
1931 	if (0 == localised.length())
1932 		::AppendMenu(menu, MF_SEPARATOR, 0, TEXT(""));
1933 	else if (enabled)
1934 		::AppendMenu(menu, MF_STRING, cmd, localised.c_str());
1935 	else
1936 		::AppendMenu(menu, MF_STRING | MF_DISABLED | MF_GRAYED, cmd, localised.c_str());
1937 }
1938 
ContextMenuMessage(UINT iMessage,WPARAM wParam,LPARAM lParam)1939 LRESULT SciTEWin::ContextMenuMessage(UINT iMessage, WPARAM wParam, LPARAM lParam) {
1940 	GUI::ScintillaWindow *w = &wEditor;
1941 	GUI::Point pt = PointFromLong(lParam);
1942 	if ((pt.x == -1) && (pt.y == -1)) {
1943 		// Caused by keyboard so display menu near caret
1944 		if (wOutput.HasFocus())
1945 			w = &wOutput;
1946 		const SA::Position position = w->CurrentPos();
1947 		pt.x = w->PointXFromPosition(position);
1948 		pt.y = w->PointYFromPosition(position);
1949 		POINT spt = {pt.x, pt.y};
1950 		::ClientToScreen(HwndOf(*w), &spt);
1951 		pt = GUI::Point(spt.x, spt.y);
1952 	} else {
1953 		const GUI::Rectangle rcEditor = wEditor.GetPosition();
1954 		if (!rcEditor.Contains(pt)) {
1955 			const GUI::Rectangle rcOutput = wOutput.GetPosition();
1956 			if (rcOutput.Contains(pt)) {
1957 				w = &wOutput;
1958 			} else {	// In frame so use default.
1959 				return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
1960 			}
1961 		}
1962 	}
1963 	menuSource = ::GetDlgCtrlID(HwndOf(*w));
1964 	ContextMenu(*w, pt, wSciTE);
1965 	return 0;
1966 }
1967 
CheckForScintillaFailure(SA::Status statusFailure)1968 void SciTEWin::CheckForScintillaFailure(SA::Status statusFailure) {
1969 	static int boxesVisible = 0;
1970 	if ((statusFailure > SA::Status::Ok) && (boxesVisible == 0)) {
1971 		boxesVisible++;
1972 		char buff[200] = "";
1973 		if (statusFailure == SA::Status::BadAlloc) {
1974 			strcpy(buff, "Memory exhausted.");
1975 		} else {
1976 			sprintf(buff, "Scintilla failed with status %d.", static_cast<int>(statusFailure));
1977 		}
1978 		strcat(buff, " SciTE will now close.");
1979 		GUI::gui_string sMessage = GUI::StringFromUTF8(buff);
1980 		::MessageBox(MainHWND(), sMessage.c_str(), TEXT("Failure in Scintilla"), MB_OK | MB_ICONERROR | MB_APPLMODAL);
1981 		exit(FALSE);
1982 	}
1983 }
1984 
WndProc(UINT iMessage,WPARAM wParam,LPARAM lParam)1985 LRESULT SciTEWin::WndProc(UINT iMessage, WPARAM wParam, LPARAM lParam) {
1986 	try {
1987 		const LRESULT uim = uniqueInstance.CheckMessage(iMessage, wParam, lParam);
1988 		if (uim != 0) {
1989 			return uim;
1990 		}
1991 
1992 		switch (iMessage) {
1993 
1994 		case WM_CREATE:
1995 			Creation();
1996 			break;
1997 
1998 		case WM_COMMAND:
1999 			Command(wParam, lParam);
2000 			break;
2001 
2002 		case WM_CONTEXTMENU:
2003 			return ContextMenuMessage(iMessage, wParam, lParam);
2004 
2005 		case WM_ENTERMENULOOP:
2006 			if (!wParam)
2007 				menuSource = 0;
2008 			break;
2009 
2010 		case WM_SYSCOMMAND:
2011 			if ((wParam == SC_MINIMIZE) && props.GetInt("minimize.to.tray")) {
2012 				MinimizeToTray();
2013 				return 0;
2014 			}
2015 			return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
2016 
2017 		case SCITE_TRAY:
2018 			if (lParam == WM_LBUTTONDOWN) {
2019 				RestoreFromTray();
2020 				::ShowWindow(MainHWND(), SW_RESTORE);
2021 				::FlashWindow(MainHWND(), FALSE);
2022 			}
2023 			break;
2024 
2025 		case SCITE_DROP:
2026 			// Open the files
2027 			while (!dropFilesQueue.empty()) {
2028 				FilePath file(dropFilesQueue.front());
2029 				dropFilesQueue.pop_front();
2030 				if (file.Exists()) {
2031 					Open(file);
2032 				} else {
2033 					GUI::gui_string msg = LocaliseMessage("Could not open file '^0'.", file.AsInternal());
2034 					WindowMessageBox(wSciTE, msg);
2035 				}
2036 			}
2037 			break;
2038 
2039 		case SCITE_WORKER:
2040 			WorkerCommand(static_cast<int>(wParam), reinterpret_cast<Worker *>(lParam));
2041 			break;
2042 
2043 		case SCITE_SHOWOUTPUT:
2044 			SetOutputVisibility(true);
2045 			break;
2046 
2047 		case WM_NOTIFY:
2048 			Notify(reinterpret_cast<SCNotification *>(lParam));
2049 			break;
2050 
2051 		case WM_KEYDOWN:
2052 			return KeyDown(wParam);
2053 
2054 		case WM_KEYUP:
2055 			return KeyUp(wParam);
2056 
2057 		case WM_APPCOMMAND:
2058 			switch (GET_APPCOMMAND_LPARAM(lParam)) {
2059 				case APPCOMMAND_BROWSER_BACKWARD:
2060 					return KeyDown(VK_BROWSER_BACK);
2061 				case APPCOMMAND_BROWSER_FORWARD:
2062 					return KeyDown(VK_BROWSER_FORWARD);
2063 				default:
2064 					return ::DefWindowProcW(MainHWND(), iMessage, wParam, lParam);
2065 			}
2066 			return TRUE;
2067 
2068 		case WM_SIZE:
2069 			if (wParam != SIZE_MINIMIZED)
2070 				SizeSubWindows();
2071 			break;
2072 
2073 		case WM_MOVE:
2074 			wEditor.CallTipCancel();
2075 			break;
2076 
2077 		case WM_GETMINMAXINFO: {
2078 				MINMAXINFO *pmmi = reinterpret_cast<MINMAXINFO *>(lParam);
2079 				if (fullScreen) {
2080 					pmmi->ptMaxSize.x = ::GetSystemMetrics(SM_CXSCREEN) +
2081 							    2 * ::GetSystemMetrics(SM_CXSIZEFRAME);
2082 					pmmi->ptMaxSize.y = ::GetSystemMetrics(SM_CYSCREEN) +
2083 							    ::GetSystemMetrics(SM_CYCAPTION) +
2084 							    ::GetSystemMetrics(SM_CYMENU) +
2085 							    2 * ::GetSystemMetrics(SM_CYSIZEFRAME);
2086 					pmmi->ptMaxTrackSize.x = pmmi->ptMaxSize.x;
2087 					pmmi->ptMaxTrackSize.y = pmmi->ptMaxSize.y;
2088 					return 0;
2089 				} else {
2090 					return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
2091 				}
2092 			}
2093 
2094 		case WM_INITMENU:
2095 			CheckMenus();
2096 			break;
2097 
2098 		case WM_CLOSE:
2099 			QuitProgram();
2100 			return 0;
2101 
2102 		case WM_QUERYENDSESSION:
2103 			QuitProgram();
2104 			return 1;
2105 
2106 		case WM_DESTROY:
2107 			break;
2108 
2109 		case WM_SETTINGCHANGE:
2110 			SettingChanged(wParam, lParam);
2111 			break;
2112 
2113 		case WM_SYSCOLORCHANGE:
2114 			SysColourChanged(wParam, lParam);
2115 			break;
2116 
2117 		case WM_DPICHANGED:
2118 			ScaleChanged(wParam, lParam);
2119 			return ::DefWindowProcW(MainHWND(), iMessage, wParam, lParam);
2120 
2121 		case WM_ACTIVATEAPP:
2122 			if (props.GetInt("selection.always.visible", 0) == 0) {
2123 				wEditor.HideSelection(!wParam);
2124 			}
2125 			// Do not want to display dialog yet as may be in middle of system mouse capture
2126 			::PostMessage(MainHWND(), WM_COMMAND, IDM_ACTIVATE, wParam);
2127 			break;
2128 
2129 		case WM_ACTIVATE:
2130 			if (wParam != WA_INACTIVE) {
2131 				if (searchStrip.visible)
2132 					searchStrip.Focus();
2133 				else if (findStrip.visible)
2134 					findStrip.Focus();
2135 				else if (replaceStrip.visible)
2136 					replaceStrip.Focus();
2137 				else if (userStrip.visible)
2138 					userStrip.Focus();
2139 				else
2140 					::SetFocus(wFocus);
2141 			}
2142 			break;
2143 
2144 		case WM_TIMER:
2145 			OnTimer();
2146 			break;
2147 
2148 		case WM_DROPFILES:
2149 			DropFiles(reinterpret_cast<HDROP>(wParam));
2150 			break;
2151 
2152 		case WM_COPYDATA:
2153 			return uniqueInstance.CopyData(reinterpret_cast<COPYDATASTRUCT *>(lParam));
2154 
2155 		default:
2156 			return ::DefWindowProcW(MainHWND(), iMessage, wParam, lParam);
2157 		}
2158 	} catch (const SA::Failure &sf) {
2159 		CheckForScintillaFailure(sf.status);
2160 	}
2161 	return 0;
2162 }
2163 
TWndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)2164 LRESULT PASCAL SciTEWin::TWndProc(
2165 	HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
2166 	if (iMessage == WM_CREATE) {
2167 		SciTEWin *scitePassed = static_cast<SciTEWin *>(SetWindowPointerFromCreate(hWnd, lParam));
2168 		scitePassed->wSciTE = hWnd;
2169 	}
2170 	// Find C++ object associated with window.
2171 	SciTEWin *scite = static_cast<SciTEWin *>(PointerFromWindow(hWnd));
2172 	// scite will be zero if WM_CREATE not seen yet
2173 	if (scite) {
2174 		return scite->WndProc(iMessage, wParam, lParam);
2175 	} else {
2176 		return ::DefWindowProcW(hWnd, iMessage, wParam, lParam);
2177 	}
2178 }
2179 
WndProc(UINT iMessage,WPARAM wParam,LPARAM lParam)2180 LRESULT ContentWin::WndProc(UINT iMessage, WPARAM wParam, LPARAM lParam) {
2181 	try {
2182 		switch (iMessage) {
2183 
2184 		case WM_CREATE:
2185 			pSciTEWin->wContent = GetID();
2186 			return ::DefWindowProc(Hwnd(), iMessage, wParam, lParam);
2187 
2188 		case WM_COMMAND:
2189 		case WM_NOTIFY:
2190 			return pSciTEWin->WndProc(iMessage, wParam, lParam);
2191 
2192 		case WM_PAINT: {
2193 				PAINTSTRUCT ps;
2194 				::BeginPaint(Hwnd(), &ps);
2195 				const GUI::Rectangle rcPaint(ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right, ps.rcPaint.bottom);
2196 				Paint(ps.hdc, rcPaint);
2197 				::EndPaint(Hwnd(), &ps);
2198 				return 0;
2199 			}
2200 
2201 		case WM_ERASEBKGND: {
2202 				RECT rc = {0, 0, 2000, 2000};
2203 				HBRUSH hbrFace = CreateSolidBrush(::GetSysColor(COLOR_3DFACE));
2204 				::FillRect(reinterpret_cast<HDC>(wParam), &rc, hbrFace);
2205 				::DeleteObject(hbrFace);
2206 				return 0;
2207 			}
2208 
2209 		case WM_LBUTTONDOWN:
2210 			pSciTEWin->ptStartDrag = PointFromLong(lParam);
2211 			capturedMouse = true;
2212 			pSciTEWin->heightOutputStartDrag = pSciTEWin->heightOutput;
2213 			::SetCapture(Hwnd());
2214 			break;
2215 
2216 		case WM_MOUSEMOVE:
2217 			if (capturedMouse) {
2218 				pSciTEWin->MoveSplit(PointFromLong(lParam));
2219 			}
2220 			break;
2221 
2222 		case WM_LBUTTONUP:
2223 			if (capturedMouse) {
2224 				pSciTEWin->MoveSplit(PointFromLong(lParam));
2225 				capturedMouse = false;
2226 				::ReleaseCapture();
2227 			}
2228 			break;
2229 
2230 		case WM_CAPTURECHANGED:
2231 			capturedMouse = false;
2232 			break;
2233 
2234 		case WM_SETCURSOR:
2235 			if (ControlIDOfCommand(static_cast<unsigned long>(lParam)) == HTCLIENT) {
2236 				const GUI::Point ptCursor = PointOfCursor();
2237 				const GUI::Rectangle rcScintilla = pSciTEWin->wEditor.GetPosition();
2238 				const GUI::Rectangle rcOutput = pSciTEWin->wOutput.GetPosition();
2239 				if (!rcScintilla.Contains(ptCursor) && !rcOutput.Contains(ptCursor)) {
2240 					::SetCursor(::LoadCursor(NULL, pSciTEWin->splitVertical ? IDC_SIZEWE : IDC_SIZENS));
2241 					return TRUE;
2242 				}
2243 			}
2244 			return ::DefWindowProc(Hwnd(), iMessage, wParam, lParam);
2245 
2246 		default:
2247 			return ::DefWindowProc(Hwnd(), iMessage, wParam, lParam);
2248 
2249 		}
2250 	} catch (...) {
2251 	}
2252 	return 0;
2253 }
2254 
2255 // Convert String from UTF-8 to doc encoding
EncodeString(const std::string & s)2256 std::string SciTEWin::EncodeString(const std::string &s) {
2257 	UINT codePageDocument = static_cast<UINT>(wEditor.CodePage());
2258 
2259 	if (codePageDocument != SA::CpUtf8) {
2260 		codePageDocument = CodePageFromCharSet(characterSet, codePageDocument);
2261 		std::wstring sWide = StringDecode(std::string(s.c_str(), s.length()), CP_UTF8);
2262 		return StringEncode(sWide, codePageDocument);
2263 	}
2264 	return SciTEBase::EncodeString(s);
2265 }
2266 
2267 // Convert String from doc encoding to UTF-8
GetRangeInUIEncoding(GUI::ScintillaWindow & win,SA::Range range)2268 std::string SciTEWin::GetRangeInUIEncoding(GUI::ScintillaWindow &win, SA::Range range) {
2269 	std::string s = SciTEBase::GetRangeInUIEncoding(win, range);
2270 
2271 	UINT codePageDocument = wEditor.CodePage();
2272 
2273 	if (codePageDocument != SA::CpUtf8) {
2274 		codePageDocument = CodePageFromCharSet(characterSet, codePageDocument);
2275 		std::wstring sWide = StringDecode(std::string(s.c_str(), s.length()), codePageDocument);
2276 		std::string sMulti = StringEncode(sWide, CP_UTF8);
2277 		return std::string(sMulti.c_str(), 0, sMulti.length());
2278 	}
2279 	return s;
2280 }
2281 
EventLoop()2282 uintptr_t SciTEWin::EventLoop() {
2283 	MSG msg;
2284 	msg.wParam = 0;
2285 	BOOL going = TRUE;
2286 	while (going) {
2287 		if (needIdle) {
2288 			const BOOL haveMessage = PeekMessageW(&msg, NULL, 0, 0, PM_NOREMOVE);
2289 			if (!haveMessage) {
2290 				OnIdle();
2291 				continue;
2292 			}
2293 		}
2294 		going = ::GetMessageW(&msg, NULL, 0, 0);
2295 		if (going > 0) {
2296 			if (!ModelessHandler(&msg)) {
2297 				if (!GetID() ||
2298 						::TranslateAcceleratorW(static_cast<HWND>(GetID()), GetAcceleratorTable(), &msg) == 0) {
2299 					::TranslateMessage(&msg);
2300 					::DispatchMessageW(&msg);
2301 				}
2302 			}
2303 		}
2304 	}
2305 	return msg.wParam;
2306 }
2307 
RestrictDLLPath()2308 static void RestrictDLLPath() noexcept {
2309 	// Try to limit the locations where DLLs will be loaded from to prevent binary planting.
2310 	// That is where a bad DLL is placed in the current directory or in the PATH.
2311 	typedef BOOL(WINAPI *SetDefaultDllDirectoriesSig)(DWORD DirectoryFlags);
2312 	typedef BOOL(WINAPI *SetDllDirectorySig)(LPCTSTR lpPathName);
2313 	HMODULE kernel32 = ::GetModuleHandle(TEXT("kernel32.dll"));
2314 	if (kernel32) {
2315 		// SetDefaultDllDirectories is stronger, limiting search path to just the application and
2316 		// system directories but is only available on Windows 8+
2317 		SetDefaultDllDirectoriesSig SetDefaultDllDirectoriesFn =
2318 			reinterpret_cast<SetDefaultDllDirectoriesSig>(::GetProcAddress(
2319 						kernel32, "SetDefaultDllDirectories"));
2320 		if (SetDefaultDllDirectoriesFn) {
2321 			SetDefaultDllDirectoriesFn(LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32);
2322 		} else {
2323 			SetDllDirectorySig SetDllDirectoryFn =
2324 				reinterpret_cast<SetDllDirectorySig>(::GetProcAddress(
2325 							kernel32, "SetDllDirectoryW"));
2326 			if (SetDllDirectoryFn) {
2327 				// For security, remove current directory from the DLL search path
2328 				SetDllDirectoryFn(TEXT(""));
2329 			}
2330 		}
2331 	}
2332 }
2333 
2334 #ifdef STATIC_BUILD
2335 extern "C" Scintilla::ILexer5 * __stdcall CreateLexer(const char *name);
2336 #endif
2337 
2338 #if defined(_MSC_VER) && defined(_PREFAST_)
2339 // Stop warning for WinMain. Microsoft headers have annotations and MinGW don't.
2340 #pragma warning(disable: 28251)
2341 #endif
2342 
WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR,int)2343 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) {
2344 
2345 	RestrictDLLPath();
2346 
2347 #ifndef NO_EXTENSIONS
2348 	MultiplexExtension multiExtender;
2349 
2350 #ifndef NO_LUA
2351 	multiExtender.RegisterExtension(LuaExtension::Instance());
2352 #endif
2353 
2354 #ifndef NO_FILER
2355 	multiExtender.RegisterExtension(DirectorExtension::Instance());
2356 #endif
2357 #endif
2358 
2359 	SciTEWin::Register(hInstance);
2360 	LexillaSetDefaultDirectory(GetSciTEPath(FilePath()).AsUTF8());
2361 #ifdef STATIC_BUILD
2362 	Scintilla_LinkLexers();
2363 	Scintilla_RegisterClasses(hInstance);
2364 	LexillaSetDefault([](const char *name) {
2365 		return CreateLexer(name);
2366 	});
2367 #else
2368 
2369 	HMODULE hmod = ::LoadLibrary(scintillaName);
2370 	if (!hmod) {
2371 		GUI::gui_string explanation = scintillaName;
2372 		explanation += TEXT(" could not be loaded.  SciTE will now close");
2373 		::MessageBox(NULL, explanation.c_str(),
2374 			     TEXT("Error loading Scintilla"), MB_OK | MB_ICONERROR);
2375 	}
2376 #endif
2377 
2378 	uintptr_t result = 0;
2379 	{
2380 #ifdef NO_EXTENSIONS
2381 		Extension *extender = 0;
2382 #else
2383 		Extension *extender = &multiExtender;
2384 #endif
2385 		SciTEWin MainWind(extender);
2386 		LPTSTR lptszCmdLine = GetCommandLine();
2387 		if (*lptszCmdLine == '\"') {
2388 			lptszCmdLine++;
2389 			while (*lptszCmdLine && (*lptszCmdLine != '\"'))
2390 				lptszCmdLine++;
2391 			if (*lptszCmdLine == '\"')
2392 				lptszCmdLine++;
2393 		} else {
2394 			while (*lptszCmdLine && (*lptszCmdLine != ' '))
2395 				lptszCmdLine++;
2396 		}
2397 		while (*lptszCmdLine == ' ')
2398 			lptszCmdLine++;
2399 		try {
2400 			MainWind.Run(lptszCmdLine);
2401 			result = MainWind.EventLoop();
2402 		} catch (const SA::Failure &sf) {
2403 			MainWind.CheckForScintillaFailure(sf.status);
2404 		}
2405 		MainWind.Finalise();
2406 	}
2407 
2408 #ifdef STATIC_BUILD
2409 	Scintilla_ReleaseResources();
2410 #else
2411 
2412 	::FreeLibrary(hmod);
2413 #endif
2414 
2415 	return static_cast<int>(result);
2416 }
2417