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