/Source/script.cpp
C++ | 9651 lines | 7330 code | 411 blank | 1910 comment | 1500 complexity | 61daa4a3e240093c68914366f52d2ccc MD5 | raw file
Possible License(s): AGPL-1.0, BSD-3-Clause
Large files files are truncated, but you can click here to view the full file
- /*
- AutoHotkey
- Copyright 2003-2009 Chris Mallett (support@autohotkey.com)
- This program is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License
- as published by the Free Software Foundation; either version 2
- of the License, or (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
- #include "stdafx.h" // pre-compiled headers
- #include "script.h"
- #include "globaldata.h" // for a lot of things
- #include "util.h" // for strlcpy() etc.
- #include "mt19937ar-cok.h" // for random number generator
- #include "window.h" // for a lot of things
- #include "application.h" // for MsgSleep()
- // Globals that are for only this module:
- #define MAX_COMMENT_FLAG_LENGTH 15
- static char g_CommentFlag[MAX_COMMENT_FLAG_LENGTH + 1] = ";"; // Adjust the below for any changes.
- static size_t g_CommentFlagLength = 1; // pre-calculated for performance
- // General note about the methods in here:
- // Want to be able to support multiple simultaneous points of execution
- // because more than one subroutine can be executing simultaneously
- // (well, more precisely, there can be more than one script subroutine
- // that's in a "currently running" state, even though all such subroutines,
- // except for the most recent one, are suspended. So keep this in mind when
- // using things such as static data members or static local variables.
- Script::Script()
- : mFirstLine(NULL), mLastLine(NULL), mCurrLine(NULL), mPlaceholderLabel(NULL), mLineCount(0)
- , mThisHotkeyName(""), mPriorHotkeyName(""), mThisHotkeyStartTime(0), mPriorHotkeyStartTime(0)
- , mEndChar(0), mThisHotkeyModifiersLR(0)
- , mNextClipboardViewer(NULL), mOnClipboardChangeIsRunning(false), mOnClipboardChangeLabel(NULL)
- , mOnExitLabel(NULL), mExitReason(EXIT_NONE)
- , mFirstLabel(NULL), mLastLabel(NULL)
- , mFirstFunc(NULL), mLastFunc(NULL)
- , mFirstTimer(NULL), mLastTimer(NULL), mTimerEnabledCount(0), mTimerCount(0)
- , mFirstMenu(NULL), mLastMenu(NULL), mMenuCount(0)
- , mVar(NULL), mVarCount(0), mVarCountMax(0), mLazyVar(NULL), mLazyVarCount(0)
- , mCurrentFuncOpenBlockCount(0), mNextLineIsFunctionBody(false)
- , mFuncExceptionVar(NULL), mFuncExceptionVarCount(0)
- , mCurrFileIndex(0), mCombinedLineNumber(0), mNoHotkeyLabels(true), mMenuUseErrorLevel(false)
- , mFileSpec(""), mFileDir(""), mFileName(""), mOurEXE(""), mOurEXEDir(""), mMainWindowTitle("")
- , mIsReadyToExecute(false), mAutoExecSectionIsRunning(false)
- , mIsRestart(false), mIsAutoIt2(false), mErrorStdOut(false)
- #ifdef AUTOHOTKEYSC
- , mCompiledHasCustomIcon(false)
- #else
- , mIncludeLibraryFunctionsThenExit(NULL)
- #endif
- , mLinesExecutedThisCycle(0), mUninterruptedLineCountMax(1000), mUninterruptibleTime(15)
- , mRunAsUser(NULL), mRunAsPass(NULL), mRunAsDomain(NULL)
- , mCustomIcon(NULL) // Normally NULL unless there's a custom tray icon loaded dynamically.
- , mCustomIconFile(NULL), mIconFrozen(false), mTrayIconTip(NULL) // Allocated on first use.
- , mCustomIconNumber(0)
- {
- // v1.0.25: mLastScriptRest and mLastPeekTime are now initialized right before the auto-exec
- // section of the script is launched, which avoids an initial Sleep(10) in ExecUntil
- // that would otherwise occur.
- *mThisMenuItemName = *mThisMenuName = '\0';
- ZeroMemory(&mNIC, sizeof(mNIC)); // Constructor initializes this, to be safe.
- mNIC.hWnd = NULL; // Set this as an indicator that it tray icon is not installed.
- // Lastly (after the above have been initialized), anything that can fail:
- if ( !(mTrayMenu = AddMenu("Tray")) ) // realistically never happens
- {
- ScriptError("No tray mem");
- ExitApp(EXIT_CRITICAL);
- }
- else
- mTrayMenu->mIncludeStandardItems = true;
- #ifdef _DEBUG
- if (ID_FILE_EXIT < ID_MAIN_FIRST) // Not a very thorough check.
- ScriptError("DEBUG: ID_FILE_EXIT is too large (conflicts with IDs reserved via ID_USER_FIRST).");
- if (MAX_CONTROLS_PER_GUI > ID_USER_FIRST - 3)
- ScriptError("DEBUG: MAX_CONTROLS_PER_GUI is too large (conflicts with IDs reserved via ID_USER_FIRST).");
- int LargestMaxParams, i, j;
- ActionTypeType *np;
- // Find the Largest value of MaxParams used by any command and make sure it
- // isn't something larger than expected by the parsing routines:
- for (LargestMaxParams = i = 0; i < g_ActionCount; ++i)
- {
- if (g_act[i].MaxParams > LargestMaxParams)
- LargestMaxParams = g_act[i].MaxParams;
- // This next part has been tested and it does work, but only if one of the arrays
- // contains exactly MAX_NUMERIC_PARAMS number of elements and isn't zero terminated.
- // Relies on short-circuit boolean order:
- for (np = g_act[i].NumericParams, j = 0; j < MAX_NUMERIC_PARAMS && *np; ++j, ++np);
- if (j >= MAX_NUMERIC_PARAMS)
- {
- ScriptError("DEBUG: At least one command has a NumericParams array that isn't zero-terminated."
- " This would result in reading beyond the bounds of the array.");
- return;
- }
- }
- if (LargestMaxParams > MAX_ARGS)
- ScriptError("DEBUG: At least one command supports more arguments than allowed.");
- if (sizeof(ActionTypeType) == 1 && g_ActionCount > 256)
- ScriptError("DEBUG: Since there are now more than 256 Action Types, the ActionTypeType"
- " typedef must be changed.");
- #endif
- }
- Script::~Script() // Destructor.
- {
- // MSDN: "Before terminating, an application must call the UnhookWindowsHookEx function to free
- // system resources associated with the hook."
- AddRemoveHooks(0); // Remove all hooks.
- if (mNIC.hWnd) // Tray icon is installed.
- Shell_NotifyIcon(NIM_DELETE, &mNIC); // Remove it.
- // Destroy any Progress/SplashImage windows that haven't already been destroyed. This is necessary
- // because sometimes these windows aren't owned by the main window:
- int i;
- for (i = 0; i < MAX_PROGRESS_WINDOWS; ++i)
- {
- if (g_Progress[i].hwnd && IsWindow(g_Progress[i].hwnd))
- DestroyWindow(g_Progress[i].hwnd);
- if (g_Progress[i].hfont1) // Destroy font only after destroying the window that uses it.
- DeleteObject(g_Progress[i].hfont1);
- if (g_Progress[i].hfont2) // Destroy font only after destroying the window that uses it.
- DeleteObject(g_Progress[i].hfont2);
- if (g_Progress[i].hbrush)
- DeleteObject(g_Progress[i].hbrush);
- }
- for (i = 0; i < MAX_SPLASHIMAGE_WINDOWS; ++i)
- {
- if (g_SplashImage[i].pic)
- g_SplashImage[i].pic->Release();
- if (g_SplashImage[i].hwnd && IsWindow(g_SplashImage[i].hwnd))
- DestroyWindow(g_SplashImage[i].hwnd);
- if (g_SplashImage[i].hfont1) // Destroy font only after destroying the window that uses it.
- DeleteObject(g_SplashImage[i].hfont1);
- if (g_SplashImage[i].hfont2) // Destroy font only after destroying the window that uses it.
- DeleteObject(g_SplashImage[i].hfont2);
- if (g_SplashImage[i].hbrush)
- DeleteObject(g_SplashImage[i].hbrush);
- }
- // It is safer/easier to destroy the GUI windows prior to the menus (especially the menu bars).
- // This is because one GUI window might get destroyed and take with it a menu bar that is still
- // in use by an existing GUI window. GuiType::Destroy() adheres to this philosophy by detaching
- // its menu bar prior to destroying its window:
- for (i = 0; i < MAX_GUI_WINDOWS; ++i)
- GuiType::Destroy(i); // Static method to avoid problems with object destroying itself.
- for (i = 0; i < GuiType::sFontCount; ++i) // Now that GUI windows are gone, delete all GUI fonts.
- if (GuiType::sFont[i].hfont)
- DeleteObject(GuiType::sFont[i].hfont);
- // The above might attempt to delete an HFONT from GetStockObject(DEFAULT_GUI_FONT), etc.
- // But that should be harmless:
- // MSDN: "It is not necessary (but it is not harmful) to delete stock objects by calling DeleteObject."
- // Above: Probably best to have removed icon from tray and destroyed any Gui/Splash windows that were
- // using it prior to getting rid of the script's custom icon below:
- if (mCustomIcon)
- DestroyIcon(mCustomIcon);
- // Since they're not associated with a window, we must free the resources for all popup menus.
- // Update: Even if a menu is being used as a GUI window's menu bar, see note above for why menu
- // destruction is done AFTER the GUI windows are destroyed:
- UserMenu *menu_to_delete;
- for (UserMenu *m = mFirstMenu; m;)
- {
- menu_to_delete = m;
- m = m->mNextMenu;
- ScriptDeleteMenu(menu_to_delete);
- // Above call should not return FAIL, since the only way FAIL can realistically happen is
- // when a GUI window is still using the menu as its menu bar. But all GUI windows are gone now.
- }
- // Since tooltip windows are unowned, they should be destroyed to avoid resource leak:
- for (i = 0; i < MAX_TOOLTIPS; ++i)
- if (g_hWndToolTip[i] && IsWindow(g_hWndToolTip[i]))
- DestroyWindow(g_hWndToolTip[i]);
- if (g_hFontSplash) // The splash window itself should auto-destroyed, since it's owned by main.
- DeleteObject(g_hFontSplash);
- if (mOnClipboardChangeLabel) // Remove from viewer chain.
- ChangeClipboardChain(g_hWnd, mNextClipboardViewer);
- // Close any open sound item to prevent hang-on-exit in certain operating systems or conditions.
- // If there's any chance that a sound was played and not closed out, or that it is still playing,
- // this check is done. Otherwise, the check is avoided since it might be a high overhead call,
- // especially if the sound subsystem part of the OS is currently swapped out or something:
- if (g_SoundWasPlayed)
- {
- char buf[MAX_PATH * 2];
- mciSendString("status " SOUNDPLAY_ALIAS " mode", buf, sizeof(buf), NULL);
- if (*buf) // "playing" or "stopped"
- mciSendString("close " SOUNDPLAY_ALIAS, NULL, 0, NULL);
- }
- #ifdef ENABLE_KEY_HISTORY_FILE
- KeyHistoryToFile(); // Close the KeyHistory file if it's open.
- #endif
- DeleteCriticalSection(&g_CriticalRegExCache); // g_CriticalRegExCache is used elsewhere for thread-safety.
- }
- ResultType Script::Init(global_struct &g, char *aScriptFilename, bool aIsRestart)
- // Returns OK or FAIL.
- // Caller has provided an empty string for aScriptFilename if this is a compiled script.
- // Otherwise, aScriptFilename can be NULL if caller hasn't determined the filename of the script yet.
- {
- mIsRestart = aIsRestart;
- char buf[2048]; // Just to make sure we have plenty of room to do things with.
- #ifdef AUTOHOTKEYSC
- // Fix for v1.0.29: Override the caller's use of __argv[0] by using GetModuleFileName(),
- // so that when the script is started from the command line but the user didn't type the
- // extension, the extension will be included. This necessary because otherwise
- // #SingleInstance wouldn't be able to detect duplicate versions in every case.
- // It also provides more consistency.
- GetModuleFileName(NULL, buf, sizeof(buf));
- #else
- if (!aScriptFilename) // v1.0.46.08: Change in policy: store the default script in the My Documents directory rather than in Program Files. It's more correct and solves issues that occur due to Vista's file-protection scheme.
- {
- // Since no script-file was specified on the command line, use the default name.
- // For backward compatibility, FIRST check if there's an AutoHotkey.ini file in the current
- // directory. If there is, that needs to be used to retain compatibility.
- aScriptFilename = NAME_P ".ini";
- if (GetFileAttributes(aScriptFilename) == 0xFFFFFFFF) // File doesn't exist, so fall back to new method.
- {
- aScriptFilename = buf;
- VarSizeType filespec_length = BIV_MyDocuments(aScriptFilename, ""); // e.g. C:\Documents and Settings\Home\My Documents
- if (filespec_length > sizeof(buf)-16) // Need room for 16 characters ('\\' + "AutoHotkey.ahk" + terminator).
- return FAIL; // Very rare, so for simplicity just abort.
- strcpy(aScriptFilename + filespec_length, "\\AutoHotkey.ahk"); // Append the filename: .ahk vs. .ini seems slightly better in terms of clarity and usefulness (e.g. the ability to double click the default script to launch it).
- // Now everything is set up right because even if aScriptFilename is a nonexistent file, the
- // user will be prompted to create it by a stage further below.
- }
- //else since the legacy .ini file exists, everything is now set up right. (The file might be a directory, but that isn't checked due to rarity.)
- }
- // In case the script is a relative filespec (relative to current working dir):
- char *unused;
- if (!GetFullPathName(aScriptFilename, sizeof(buf), buf, &unused)) // This is also relied upon by mIncludeLibraryFunctionsThenExit. Succeeds even on nonexistent files.
- return FAIL; // Due to rarity, no error msg, just abort.
- #endif
- // Using the correct case not only makes it look better in title bar & tray tool tip,
- // it also helps with the detection of "this script already running" since otherwise
- // it might not find the dupe if the same script name is launched with different
- // lowercase/uppercase letters:
- ConvertFilespecToCorrectCase(buf); // This might change the length, e.g. due to expansion of 8.3 filename.
- char *filename_marker;
- if ( !(filename_marker = strrchr(buf, '\\')) )
- filename_marker = buf;
- else
- ++filename_marker;
- if ( !(mFileSpec = SimpleHeap::Malloc(buf)) ) // The full spec is stored for convenience, and it's relied upon by mIncludeLibraryFunctionsThenExit.
- return FAIL; // It already displayed the error for us.
- filename_marker[-1] = '\0'; // Terminate buf in this position to divide the string.
- size_t filename_length = strlen(filename_marker);
- if ( mIsAutoIt2 = (filename_length >= 4 && !stricmp(filename_marker + filename_length - 4, EXT_AUTOIT2)) )
- {
- // Set the old/AutoIt2 defaults for maximum safety and compatibilility.
- // Standalone EXEs (compiled scripts) are always considered to be non-AutoIt2 (otherwise,
- // the user should probably be using the AutoIt2 compiler).
- g_AllowSameLineComments = false;
- g_EscapeChar = '\\';
- g.TitleFindFast = true; // In case the normal default is false.
- g.DetectHiddenText = false;
- // Make the mouse fast like AutoIt2, but not quite insta-move. 2 is expected to be more
- // reliable than 1 since the AutoIt author said that values less than 2 might cause the
- // drag to fail (perhaps just for specific apps, such as games):
- g.DefaultMouseSpeed = 2;
- g.KeyDelay = 20;
- g.WinDelay = 500;
- g.LinesPerCycle = 1;
- g.IntervalBeforeRest = -1; // i.e. this method is disabled by default for AutoIt2 scripts.
- // Reduce max params so that any non escaped delimiters the user may be using literally
- // in "window text" will still be considered literal, rather than as delimiters for
- // args that are not supported by AutoIt2, such as exclude-title, exclude-text, MsgBox
- // timeout, etc. Note: Don't need to change IfWinExist and such because those already
- // have special handling to recognize whether exclude-title is really a valid command
- // instead (e.g. IfWinExist, title, text, Gosub, something).
- // NOTE: DO NOT ADD the IfWin command series to this section, since there is special handling
- // for parsing those commands to figure out whether they're being used in the old AutoIt2
- // style or the new Exclude Title/Text mode.
- // v1.0.40.02: The following is no longer done because a different mechanism is required now
- // that the ARGn macros do not check whether mArgc is too small and substitute an empty string
- // (instead, there is a loop in ExpandArgs that puts an empty string in each sArgDeref entry
- // for which the script omitted a parameter [and that loop relies on MaxParams being absolutely
- // accurate rather than conditional upon whether the script is of type ".aut"]).
- //g_act[ACT_FILESELECTFILE].MaxParams -= 2;
- //g_act[ACT_FILEREMOVEDIR].MaxParams -= 1;
- //g_act[ACT_MSGBOX].MaxParams -= 1;
- //g_act[ACT_INIREAD].MaxParams -= 1;
- //g_act[ACT_STRINGREPLACE].MaxParams -= 1;
- //g_act[ACT_STRINGGETPOS].MaxParams -= 2;
- //g_act[ACT_WINCLOSE].MaxParams -= 3; // -3 for these two, -2 for the others.
- //g_act[ACT_WINKILL].MaxParams -= 3;
- //g_act[ACT_WINACTIVATE].MaxParams -= 2;
- //g_act[ACT_WINMINIMIZE].MaxParams -= 2;
- //g_act[ACT_WINMAXIMIZE].MaxParams -= 2;
- //g_act[ACT_WINRESTORE].MaxParams -= 2;
- //g_act[ACT_WINHIDE].MaxParams -= 2;
- //g_act[ACT_WINSHOW].MaxParams -= 2;
- //g_act[ACT_WINSETTITLE].MaxParams -= 2;
- //g_act[ACT_WINGETTITLE].MaxParams -= 2;
- }
- if ( !(mFileDir = SimpleHeap::Malloc(buf)) )
- return FAIL; // It already displayed the error for us.
- if ( !(mFileName = SimpleHeap::Malloc(filename_marker)) )
- return FAIL; // It already displayed the error for us.
- #ifdef AUTOHOTKEYSC
- // Omit AutoHotkey from the window title, like AutoIt3 does for its compiled scripts.
- // One reason for this is to reduce backlash if evil-doers create viruses and such
- // with the program:
- snprintf(buf, sizeof(buf), "%s\\%s", mFileDir, mFileName);
- #else
- snprintf(buf, sizeof(buf), "%s\\%s - %s", mFileDir, mFileName, NAME_PV);
- #endif
- if ( !(mMainWindowTitle = SimpleHeap::Malloc(buf)) )
- return FAIL; // It already displayed the error for us.
- // It may be better to get the module name this way rather than reading it from the registry
- // (though it might be more proper to parse it out of the command line args or something),
- // in case the user has moved it to a folder other than the install folder, hasn't installed it,
- // or has renamed the EXE file itself. Also, enclose the full filespec of the module in double
- // quotes since that's how callers usually want it because ActionExec() currently needs it that way:
- *buf = '"';
- if (GetModuleFileName(NULL, buf + 1, sizeof(buf) - 2)) // -2 to leave room for the enclosing double quotes.
- {
- size_t buf_length = strlen(buf);
- buf[buf_length++] = '"';
- buf[buf_length] = '\0';
- if ( !(mOurEXE = SimpleHeap::Malloc(buf)) )
- return FAIL; // It already displayed the error for us.
- else
- {
- char *last_backslash = strrchr(buf, '\\');
- if (!last_backslash) // probably can't happen due to the nature of GetModuleFileName().
- mOurEXEDir = "";
- last_backslash[1] = '\0'; // i.e. keep the trailing backslash for convenience.
- if ( !(mOurEXEDir = SimpleHeap::Malloc(buf + 1)) ) // +1 to omit the leading double-quote.
- return FAIL; // It already displayed the error for us.
- }
- }
- return OK;
- }
-
- ResultType Script::CreateWindows()
- // Returns OK or FAIL.
- {
- if (!mMainWindowTitle || !*mMainWindowTitle) return FAIL; // Init() must be called before this function.
- // Register a window class for the main window:
- WNDCLASSEX wc = {0};
- wc.cbSize = sizeof(wc);
- wc.lpszClassName = WINDOW_CLASS_MAIN;
- wc.hInstance = g_hInstance;
- wc.lpfnWndProc = MainWindowProc;
- // The following are left at the default of NULL/0 set higher above:
- //wc.style = 0; // CS_HREDRAW | CS_VREDRAW
- //wc.cbClsExtra = 0;
- //wc.cbWndExtra = 0;
- wc.hIcon = wc.hIconSm = (HICON)LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_MAIN), IMAGE_ICON, 0, 0, LR_SHARED); // Use LR_SHARED to conserve memory (since the main icon is loaded for so many purposes).
- wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
- wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); // Needed for ProgressBar. Old: (HBRUSH)GetStockObject(WHITE_BRUSH);
- wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU_MAIN); // NULL; // "MainMenu";
- if (!RegisterClassEx(&wc))
- {
- MsgBox("RegClass"); // Short/generic msg since so rare.
- return FAIL;
- }
- // Register a second class for the splash window. The only difference is that
- // it doesn't have the menu bar:
- wc.lpszClassName = WINDOW_CLASS_SPLASH;
- wc.lpszMenuName = NULL; // Override the non-NULL value set higher above.
- if (!RegisterClassEx(&wc))
- {
- MsgBox("RegClass"); // Short/generic msg since so rare.
- return FAIL;
- }
- char class_name[64];
- HWND fore_win = GetForegroundWindow();
- bool do_minimize = !fore_win || (GetClassName(fore_win, class_name, sizeof(class_name))
- && !stricmp(class_name, "Shell_TrayWnd")); // Shell_TrayWnd is the taskbar's class on Win98/XP and probably the others too.
- // Note: the title below must be constructed the same was as is done by our
- // WinMain() (so that we can detect whether this script is already running)
- // which is why it's standardized in g_script.mMainWindowTitle.
- // Create the main window. Prevent momentary disruption of Start Menu, which
- // some users understandably don't like, by omitting the taskbar button temporarily.
- // This is done because testing shows that minimizing the window further below, even
- // though the window is hidden, would otherwise briefly show the taskbar button (or
- // at least redraw the taskbar). Sometimes this isn't noticeable, but other times
- // (such as when the system is under heavy load) a user reported that it is quite
- // noticeable. WS_EX_TOOLWINDOW is used instead of WS_EX_NOACTIVATE because
- // WS_EX_NOACTIVATE is available only on 2000/XP.
- if ( !(g_hWnd = CreateWindowEx(do_minimize ? WS_EX_TOOLWINDOW : 0
- , WINDOW_CLASS_MAIN
- , mMainWindowTitle
- , WS_OVERLAPPEDWINDOW // Style. Alt: WS_POPUP or maybe 0.
- , CW_USEDEFAULT // xpos
- , CW_USEDEFAULT // ypos
- , CW_USEDEFAULT // width
- , CW_USEDEFAULT // height
- , NULL // parent window
- , NULL // Identifies a menu, or specifies a child-window identifier depending on the window style
- , g_hInstance // passed into WinMain
- , NULL)) ) // lpParam
- {
- MsgBox("CreateWindow"); // Short msg since so rare.
- return FAIL;
- }
- #ifdef AUTOHOTKEYSC
- HMENU menu = GetMenu(g_hWnd);
- // Disable the Edit menu item, since it does nothing for a compiled script:
- EnableMenuItem(menu, ID_FILE_EDITSCRIPT, MF_DISABLED | MF_GRAYED);
- EnableOrDisableViewMenuItems(menu, MF_DISABLED | MF_GRAYED); // Fix for v1.0.47.06: No point in checking g_AllowMainWindow because the script hasn't starting running yet, so it will always be false.
- // But leave the ID_VIEW_REFRESH menu item enabled because if the script contains a
- // command such as ListLines in it, Refresh can be validly used.
- #endif
- if ( !(g_hWndEdit = CreateWindow("edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER
- | ES_LEFT | ES_MULTILINE | ES_READONLY | WS_VSCROLL // | WS_HSCROLL (saves space)
- , 0, 0, 0, 0, g_hWnd, (HMENU)1, g_hInstance, NULL)) )
- {
- MsgBox("CreateWindow"); // Short msg since so rare.
- return FAIL;
- }
- // FONTS: The font used by default, at least on XP, is GetStockObject(SYSTEM_FONT).
- // It seems preferable to smaller fonts such DEFAULT_GUI_FONT(DEFAULT_GUI_FONT).
- // For more info on pre-loaded fonts (not too many choices), see MSDN's GetStockObject().
- //SendMessage(g_hWndEdit, WM_SETFONT, (WPARAM)GetStockObject(SYSTEM_FONT), 0);
- // v1.0.30.05: Specifying a limit of zero opens the control to its maximum text capacity,
- // which removes the 32K size restriction. Testing shows that this does not increase the actual
- // amount of memory used for controls containing small amounts of text. All it does is allow
- // the control to allocate more memory as needed. By specifying zero, a max
- // of 64K becomes available on Windows 9x, and perhaps as much as 4 GB on NT/2k/XP.
- SendMessage(g_hWndEdit, EM_LIMITTEXT, 0, 0);
- // Some of the MSDN docs mention that an app's very first call to ShowWindow() makes that
- // function operate in a special mode. Therefore, it seems best to get that first call out
- // of the way to avoid the possibility that the first-call behavior will cause problems with
- // our normal use of ShowWindow() below and other places. Also, decided to ignore nCmdShow,
- // to avoid any momentary visual effects on startup.
- // Update: It's done a second time because the main window might now be visible if the process
- // that launched ours specified that. It seems best to override the requested state because
- // some calling processes might specify "maximize" or "shownormal" as generic launch method.
- // The script can display it's own main window with ListLines, etc.
- // MSDN: "the nCmdShow value is ignored in the first call to ShowWindow if the program that
- // launched the application specifies startup information in the structure. In this case,
- // ShowWindow uses the information specified in the STARTUPINFO structure to show the window.
- // On subsequent calls, the application must call ShowWindow with nCmdShow set to SW_SHOWDEFAULT
- // to use the startup information provided by the program that launched the application."
- ShowWindow(g_hWnd, SW_HIDE);
- ShowWindow(g_hWnd, SW_HIDE);
- // Now that the first call to ShowWindow() is out of the way, minimize the main window so that
- // if the script is launched from the Start Menu (and perhaps other places such as the
- // Quick-launch toolbar), the window that was active before the Start Menu was displayed will
- // become active again. But as of v1.0.25.09, this minimize is done more selectively to prevent
- // the launch of a script from knocking the user out of a full-screen game or other application
- // that would be disrupted by an SW_MINIMIZE:
- if (do_minimize)
- {
- ShowWindow(g_hWnd, SW_MINIMIZE);
- SetWindowLong(g_hWnd, GWL_EXSTYLE, 0); // Give the main window back its taskbar button.
- }
- // Note: When the window is not minimized, task manager reports that a simple script (such as
- // one consisting only of the single line "#Persistent") uses 2600 KB of memory vs. ~452 KB if
- // it were immediately minimized. That is probably just due to the vagaries of how the OS
- // manages windows and memory and probably doesn't actually impact system performance to the
- // degree indicated. In other words, it's hard to imagine that the failure to do
- // ShowWidnow(g_hWnd, SW_MINIMIZE) unconditionally upon startup (which causes the side effects
- // discussed further above) significantly increases the actual memory load on the system.
- g_hAccelTable = LoadAccelerators(g_hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR1));
- if (g_NoTrayIcon)
- mNIC.hWnd = NULL; // Set this as an indicator that tray icon is not installed.
- else
- // Even if the below fails, don't return FAIL in case the user is using a different shell
- // or something. In other words, it is expected to fail under certain circumstances and
- // we want to tolerate that:
- CreateTrayIcon();
- if (mOnClipboardChangeLabel)
- mNextClipboardViewer = SetClipboardViewer(g_hWnd);
- return OK;
- }
- void Script::EnableOrDisableViewMenuItems(HMENU aMenu, UINT aFlags)
- {
- EnableMenuItem(aMenu, ID_VIEW_KEYHISTORY, aFlags);
- EnableMenuItem(aMenu, ID_VIEW_LINES, aFlags);
- EnableMenuItem(aMenu, ID_VIEW_VARIABLES, aFlags);
- EnableMenuItem(aMenu, ID_VIEW_HOTKEYS, aFlags);
- }
- void Script::CreateTrayIcon()
- // It is the caller's responsibility to ensure that the previous icon is first freed/destroyed
- // before calling us to install a new one. However, that is probably not needed if the Explorer
- // crashed, since the memory used by the tray icon was probably destroyed along with it.
- {
- ZeroMemory(&mNIC, sizeof(mNIC)); // To be safe.
- // Using NOTIFYICONDATA_V2_SIZE vs. sizeof(NOTIFYICONDATA) improves compatibility with Win9x maybe.
- // MSDN: "Using [NOTIFYICONDATA_V2_SIZE] for cbSize will allow your application to use NOTIFYICONDATA
- // with earlier Shell32.dll versions, although without the version 6.0 enhancements."
- // Update: Using V2 gives an compile error so trying V1. Update: Trying sizeof(NOTIFYICONDATA)
- // for compatibility with VC++ 6.x. This is also what AutoIt3 uses:
- mNIC.cbSize = sizeof(NOTIFYICONDATA); // NOTIFYICONDATA_V1_SIZE
- mNIC.hWnd = g_hWnd;
- mNIC.uID = AHK_NOTIFYICON; // This is also used for the ID, see TRANSLATE_AHK_MSG for details.
- mNIC.uFlags = NIF_MESSAGE | NIF_TIP | NIF_ICON;
- mNIC.uCallbackMessage = AHK_NOTIFYICON;
- #ifdef AUTOHOTKEYSC
- // i.e. don't override the user's custom icon:
- mNIC.hIcon = mCustomIcon ? mCustomIcon : (HICON)LoadImage(g_hInstance, MAKEINTRESOURCE(mCompiledHasCustomIcon ? IDI_MAIN : g_IconTray), IMAGE_ICON, 0, 0, LR_SHARED);
- #else
- mNIC.hIcon = mCustomIcon ? mCustomIcon : (HICON)LoadImage(g_hInstance, MAKEINTRESOURCE(g_IconTray), IMAGE_ICON, 0, 0, LR_SHARED); // Use LR_SHARED to conserve memory (since the main icon is loaded for so many purposes).
- #endif
- UPDATE_TIP_FIELD
- // If we were called due to an Explorer crash, I don't think it's necessary to call
- // Shell_NotifyIcon() to remove the old tray icon because it was likely destroyed
- // along with Explorer. So just add it unconditionally:
- if (!Shell_NotifyIcon(NIM_ADD, &mNIC))
- mNIC.hWnd = NULL; // Set this as an indicator that tray icon is not installed.
- }
- void Script::UpdateTrayIcon(bool aForceUpdate)
- {
- if (!mNIC.hWnd) // tray icon is not installed
- return;
- static bool icon_shows_paused = false;
- static bool icon_shows_suspended = false;
- if (!aForceUpdate && (mIconFrozen || (g->IsPaused == icon_shows_paused && g_IsSuspended == icon_shows_suspended)))
- return; // it's already in the right state
- int icon;
- if (g->IsPaused && g_IsSuspended)
- icon = IDI_PAUSE_SUSPEND;
- else if (g->IsPaused)
- icon = IDI_PAUSE;
- else if (g_IsSuspended)
- icon = g_IconTraySuspend;
- else
- #ifdef AUTOHOTKEYSC
- icon = mCompiledHasCustomIcon ? IDI_MAIN : g_IconTray; // i.e. don't override the user's custom icon.
- #else
- icon = g_IconTray;
- #endif
- // Use the custom tray icon if the icon is normal (non-paused & non-suspended):
- mNIC.hIcon = (mCustomIcon && (mIconFrozen || (!g->IsPaused && !g_IsSuspended))) ? mCustomIcon
- : (HICON)LoadImage(g_hInstance, MAKEINTRESOURCE(icon), IMAGE_ICON, 0, 0, LR_SHARED); // Use LR_SHARED for simplicity and performance more than to conserve memory in this case.
- if (Shell_NotifyIcon(NIM_MODIFY, &mNIC))
- {
- icon_shows_paused = g->IsPaused;
- icon_shows_suspended = g_IsSuspended;
- }
- // else do nothing, just leave it in the same state.
- }
- ResultType Script::AutoExecSection()
- // Returns FAIL if can't run due to critical error. Otherwise returns OK.
- {
- // Now that g_MaxThreadsTotal has been permanently set by the processing of script directives like
- // #MaxThreads, an appropriately sized array can be allocated:
- if ( !(g_array = (global_struct *)malloc((g_MaxThreadsTotal+TOTAL_ADDITIONAL_THREADS) * sizeof(global_struct))) )
- return FAIL; // Due to rarity, just abort. It wouldn't be safe to run ExitApp() due to possibility of an OnExit routine.
- CopyMemory(g_array, g, sizeof(global_struct)); // Copy the temporary/startup "g" into array[0] to preserve historical behaviors that may rely on the idle thread starting with that "g".
- g = g_array; // Must be done after above.
- // v1.0.48: Due to switching from SET_UNINTERRUPTIBLE_TIMER to IsInterruptible():
- // In spite of the comments in IsInterruptible(), periodically have a timer call IsInterruptible() due to
- // the following scenario:
- // - Interrupt timeout is 60 seconds (or 60 milliseconds for that matter).
- // - For some reason IsInterrupt() isn't called for 24+ hours even though there is a current/active thread.
- // - RefreshInterruptibility() fires at 23 hours and marks the thread interruptible.
- // - Sometime after that, one of the following happens:
- // Computer is suspended/hibernated and stays that way for 50+ days.
- // IsInterrupt() is never called (except by RefreshInterruptibility()) for 50+ days.
- // (above is currently unlikely because MSG_FILTER_MAX calls IsInterruptible())
- // In either case, RefreshInterruptibility() has prevented the uninterruptibility duration from being
- // wrongly extended by up to 100% of g_script.mUninterruptibleTime. This isn't a big deal if
- // g_script.mUninterruptibleTime is low (like it almost always is); but if it's fairly large, say an hour,
- // this can prevent an unwanted extension of up to 1 hour.
- // Although any call frequency less than 49.7 days should work, currently calling once per 23 hours
- // in case any older operating systems have a SetTimer() limit of less than 0x7FFFFFFF (and also to make
- // it less likely that a long suspend/hibernate would cause the above issue). The following was
- // actually tested on Windows XP and a message does indeed arrive 23 hours after the script starts.
- SetTimer(g_hWnd, TIMER_ID_REFRESH_INTERRUPTIBILITY, 23*60*60*1000, RefreshInterruptibility); // 3rd param must not exceed 0x7FFFFFFF (2147483647; 24.8 days).
- ResultType ExecUntil_result;
- if (!mFirstLine) // In case it's ever possible to be empty.
- ExecUntil_result = OK;
- // And continue on to do normal exit routine so that the right ExitCode is returned by the program.
- else
- {
- // Choose a timeout that's a reasonable compromise between the following competing priorities:
- // 1) That we want hotkeys to be responsive as soon as possible after the program launches
- // in case the user launches by pressing ENTER on a script, for example, and then immediately
- // tries to use a hotkey. In addition, we want any timed subroutines to start running ASAP
- // because in rare cases the user might rely upon that happening.
- // 2) To support the case when the auto-execute section never finishes (such as when it contains
- // an infinite loop to do background processing), yet we still want to allow the script
- // to put custom defaults into effect globally (for things such as KeyDelay).
- // Obviously, the above approach has its flaws; there are ways to construct a script that would
- // result in unexpected behavior. However, the combination of this approach with the fact that
- // the global defaults are updated *again* when/if the auto-execute section finally completes
- // raises the expectation of proper behavior to a very high level. In any case, I'm not sure there
- // is any better approach that wouldn't break existing scripts or require a redesign of some kind.
- // If this method proves unreliable due to disk activity slowing the program down to a crawl during
- // the critical milliseconds after launch, one thing that might fix that is to have ExecUntil()
- // be forced to run a minimum of, say, 100 lines (if there are that many) before allowing the
- // timer expiration to have its effect. But that's getting complicated and I'd rather not do it
- // unless someone actually reports that such a thing ever happens. Still, to reduce the chance
- // of such a thing ever happening, it seems best to boost the timeout from 50 up to 100:
- SET_AUTOEXEC_TIMER(100);
- mAutoExecSectionIsRunning = true;
- // v1.0.25: This is now done here, closer to the actual execution of the first line in the script,
- // to avoid an unnecessary Sleep(10) that would otherwise occur in ExecUntil:
- mLastScriptRest = mLastPeekTime = GetTickCount();
- ++g_nThreads;
- ExecUntil_result = mFirstLine->ExecUntil(UNTIL_RETURN); // Might never return (e.g. infinite loop or ExitApp).
- --g_nThreads;
- // Our caller will take care of setting g_default properly.
- KILL_AUTOEXEC_TIMER // See also: AutoExecSectionTimeout().
- mAutoExecSectionIsRunning = false;
- }
- // REMEMBER: The ExecUntil() call above will never return if the AutoExec section never finishes
- // (e.g. infinite loop) or it uses Exit/ExitApp.
- // The below is done even if AutoExecSectionTimeout() already set the values once.
- // This is because when the AutoExecute section finally does finish, by definition it's
- // supposed to store the global settings that are currently in effect as the default values.
- // In other words, the only purpose of AutoExecSectionTimeout() is to handle cases where
- // the AutoExecute section takes a long time to complete, or never completes (perhaps because
- // it is being used by the script as a "backround thread" of sorts):
- // Save the values of KeyDelay, WinDelay etc. in case they were changed by the auto-execute part
- // of the script. These new defaults will be put into effect whenever a new hotkey subroutine
- // is launched. Each launched subroutine may then change the values for its own purposes without
- // affecting the settings for other subroutines:
- global_clear_state(*g); // Start with a "clean slate" in both g_default and g (in case things like InitNewThread() check some of the values in g prior to launching a new thread).
- // Always want g_default.AllowInterruption==true so that InitNewThread() doesn't have to
- // set it except when Critical or "Thread Interrupt" require it. If the auto-execute section ended
- // without anyone needing to call IsInterruptible() on it, AllowInterruption could be false
- // even when Critical is off.
- // Even if the still-running AutoExec section has turned on Critical, the assignment below is still okay
- // because InitNewThread() adjusts AllowInterruption based on the value of ThreadIsCritical.
- // See similar code in AutoExecSectionTimeout().
- g->AllowThreadToBeInterrupted = true; // Mostly for the g_default line below. See comments above.
- CopyMemory(&g_default, g, sizeof(global_struct)); // g->IsPaused has been set to false higher above in case it's ever possible that it's true as a result of AutoExecSection().
- // After this point, the values in g_default should never be changed.
- global_maximize_interruptibility(*g); // See below.
- // Now that any changes made by the AutoExec section have been saved to g_default (including
- // the commands Critical and Thread), ensure that the very first g-item is always interruptible.
- // This avoids having to treat the first g-item as special in various places.
- // It seems best to set ErrorLevel to NONE after the auto-execute part of the script is done.
- // However, it isn't set to NONE right before launching each new thread (e.g. hotkey subroutine)
- // because it's more flexible that way (i.e. the user may want one hotkey subroutine to use the value
- // of ErrorLevel set by another). This reset was also done by LoadFromFile(), but it is done again
- // here in case the auto-execute section changed it:
- g_ErrorLevel->Assign(ERRORLEVEL_NONE);
- // BEFORE DOING THE BELOW, "g" and "g_default" should be set up properly in case there's an OnExit
- // routine (even non-persistent scripts can have one).
- // If no hotkeys are in effect, the user hasn't requested a hook to be activated, and the script
- // doesn't contain the #Persistent directive we're done unless there is an OnExit subroutine and it
- // doesn't do "ExitApp":
- if (!IS_PERSISTENT) // Resolve macro again in case any of its components changed since the last time.
- g_script.ExitApp(ExecUntil_result == FAIL ? EXIT_ERROR : EXIT_EXIT);
- return OK;
- }
- ResultType Script::Edit()
- {
- #ifdef AUTOHOTKEYSC
- return OK; // Do nothing.
- #else
- // This is here in case a compiled script ever uses the Edit command. Since the "Edit This
- // Script" menu item is not available for compiled scripts, it can't be called from there.
- TitleMatchModes old_mode = g->TitleMatchMode;
- g->TitleMatchMode = FIND_ANYWHERE;
- HWND hwnd = WinExist(*g, mFileName, "", mMainWindowTitle, ""); // Exclude our own main window.
- g->TitleMatchMode = old_mode;
- if (hwnd)
- {
- char class_name[32];
- GetClassName(hwnd, class_name, sizeof(class_name));
- if (!strcmp(class_name, "#32770") || !strnicmp(class_name, "AutoHotkey", 10)) // MessageBox(), InputBox(), FileSelectFile(), or GUI/script-owned window.
- hwnd = NULL; // Exclude it from consideration.
- }
- if (hwnd) // File appears to already be open for editing, so use the current window.
- SetForegroundWindowEx(hwnd);
- else
- {
- char buf[MAX_PATH * 2];
- // Enclose in double quotes anything that might contain spaces since the CreateProcess()
- // method, which is attempted first, is more likely to succeed. This is because it uses
- // the command line method of creating the process, with everything all lumped together:
- snprintf(buf, sizeof(buf), "\"%s\"", mFileSpec);
- if (!ActionExec("edit", buf, mFileDir, false)) // Since this didn't work, try notepad.
- {
- // v1.0.40.06: Try to open .ini files first with their associated editor rather than trying the
- // "edit" verb on them:
- char *file_ext;
- if ( !(file_ext = strrchr(mFileName, '.')) || stricmp(file_ext, ".ini")
- || !ActionExec("open", buf, mFileDir, false) ) // Relies on short-circuit boolean order.
- {
- // Even though notepad properly handles filenames with spaces in them under WinXP,
- // even without double quotes around them, it seems safer and more correct to always
- // enclose the filename in double quotes for maximum compatibility with all OSes:
- if (!ActionExec("notepad.exe", buf, mFileDir, false))
- MsgBox("Could not open script."); // Short message since so rare.
- }
- }
- }
- return OK;
- #endif
- }
- ResultType Script::Reload(bool aDisplayErrors)
- {
- // The new instance we're about to start will tell our process to stop, or it will display
- // a syntax error or some other error, in which case our process will still be running:
- #ifdef AUTOHOTKEYSC
- // This is here in case a compiled script ever uses the Reload command. Since the "Reload This
- // Script" menu item is not available for compiled scripts, it can't be called from there.
- return g_script.ActionExec(mOurEXE, "/restart", g_WorkingDirOrig, aDisplayErrors);
- #else
- char arg_string[MAX_PATH + 512];
- snprintf(arg_string, sizeof(arg_string), "/restart \"%s\"", mFileSpec);
- return g_script.ActionExec(mOurEXE, arg_string, g_WorkingDirOrig, aDisplayErrors);
- #endif
- }
- ResultType Script::ExitApp(ExitReasons aExitReason, char *aBuf, int aExitCode)
- // Normal exit (if aBuf is NULL), or a way to exit immediately on error (which is mostly
- // for times when it would be unsafe to call MsgBox() due to the possibility that it would
- // make the situation even worse).
- {
- mExitReason = aExitReason;
- bool terminate_afterward = aBuf && !*aBuf;
- if (aBuf && *aBuf)
- {
- char buf[1024];
- // No more than size-1 chars will be written and string will be terminated:
- snprintf(buf, sizeof(buf), "Critical Error: %s\n\n" WILL_EXIT, aBuf);
- // To avoid chance of more errors, don't use MsgBox():
- MessageBox(g_hWnd, buf, g_script.mFileSpec, MB_OK | MB_SETFOREGROUND | MB_APPLMODAL);
- TerminateApp(CRITICAL_ERROR); // Only after the above.
- }
- // Otherwise, it's not a critical error. Note that currently, mOnExitLabel can only be
- // non-NULL if the script is in a runnable state (since registering an OnExit label requires
- // that a script command has executed to do it). If this ever changes, the !mIsReadyToExecute
- // condition should be added to the below if statement:
- static bool sExitLabelIsRunning = false;
- if (!mOnExitLabel || sExitLabelIsRunning) // || !mIsReadyToExecute
- // In the case of sExitLabelIsRunning == true:
- // There is another instance of this function beneath us on the stack. Since we have
- // been called, this is a true exit condition and we exit immediately.
- // MUST NOT create a new thread when sExitLabelIsRunning because g_array allows only one
- // extra thread for ExitApp() (which allows it to run even when MAX_THREADS_EMERGENCY has
- // been reached). See TOTAL_ADDITIONAL_THREADS.
- TerminateApp(aExitCode);
- // Otherwise, the script contains the special RunOnExit label that we will run here instead
- // of exiting. And since it does, we know that the script is in a ready-to-execute state
- // because that is the only way an OnExit label could have been defined in the first place.
- // Usually, the RunOnExit subroutine will contain an Exit or ExitApp statement
- // which results in a recursive call to this function, but this is not required (e.g. the
- // Exit subroutine could display an "Are you sure?" prompt, and if the user chooses "No",
- // the Exit sequence can be aborted by simply not calling ExitApp and letting the thread
- // we create below end normally).
- // Next, save the current state of the globals so that they can be restored just prior
- // to returning to our caller:
- char ErrorLevel_saved[ERRORLEVEL_SAVED_SIZE];
- strlcpy(ErrorLevel_saved, g_ErrorLevel->Contents(), sizeof(ErrorLevel_saved)); // Save caller's errorlevel.
- InitNewThread(0, true, true, ACT_INVALID); // Uninterruptibility is handled below. Since this special thread should always run, no checking of g_MaxThreadsTotal is done before calling this.
- // Turn on uninterruptibility to forbid any hotkeys, timers, or user defined menu items
- // to interrupt. This is mainly done for peace-of-mind (since possible interactions due to
- // interruptions have not been studied) and the fact that this most users would not want this
- // subroutine to be interruptible (it usually runs quickly anyway). Another reason to make
- // it non-interruptible is that some OnExit subroutines might destruct things used by the
- // script's hotkeys/timers/menu items, and activating these items during the deconstruction
- // would not be safe. Finally, if a logoff or shutdown is occurring, it seems best to prevent
- // timed subroutines from running -- which might take too much time and prevent the exit from
- // occurring in a timely fashion. An option can be added via the FutureUse param to make it
- // interruptible if there is ever a demand for that.
- // UPDATE: g_AllowInterruption is now used instead of g->AllowThreadToBeInterrupted for two reasons:
- // 1) It avoids the need to do "int mUninterruptedLineCountMax_prev = g_script.mUninterruptedLineCountMax;"
- // (Disable this item so that ExecUntil() won't automatically make our new thread uninterruptible
- // after it has executed a certain number of lines).
- // 2) Mostly obsolete: If the thread we're interrupting is uninterruptible, the uinterruptible timer
- // might be currently pending. When it fires, it would make the OnExit subroutine interruptible
- // rather than the underlying subroutine. The above fixes the first part of that problem.
- // The 2nd part is fixed by reinstating the timer when the uninterruptible thread is resumed.
- // This special handling is only necessary here -- not in other places where new threads are
- // created -- because OnExit is the only type of thread that can interrupt an uninterruptible
- // thread.
- BOOL g_AllowInterruption_prev = g_AllowInterruption; // Save current setting.
- g_AllowInterruption = FALSE; // Mark the thread just created above as permanently uninterruptible (i.e. until it finishes and is destroyed).
- sExitLabelIsRunning = true;
- if (mOnExitLabel->Execute() == FAIL)
- // If the subroutine encounters a failure condition such as a runtime error, exit immediately.
- // Otherwise, there will be no way to exit the script if the subroutine fails on each attempt.
- TerminateApp(aExitCode);
- sExitLabelIsRunning = false; // In case the user wanted the thread to end normally (see above).
- if (terminate_afterward)
- TerminateApp(aExitCode);
- // Otherwise:
- ResumeUnderlyingThread(ErrorLevel_saved);
- g_AllowInterruption = g_AllowInterruption_prev; // Restore original setting.
- return OK; // for caller convenience.
- }
- void Script::TerminateApp(int aExitCode)
- // Note that g_script's destructor takes care of most other cleanup work, such as destroying
- // tray icons, menus, and unowned windows such as ToolTip.
- {
- // We call DestroyWindow() because MainWindowProc() has left that up to us.
- // DestroyWindow() will cause MainWindowProc() to immediately receive and process the
- // WM_DESTROY msg, which should in turn result in any child windows being destroyed
- // and other cleanup being done:
- if (IsWindow(g_hWnd)) // Adds peace of mind in case WM_DESTROY was already received in some unusual way.
- {
- g_DestroyWindowCalled = true;
- DestroyWindow(g_hWnd);
- }
- Hotkey::AllDestructAndExit(aExitCode);
- }
- #ifdef AUTOHOTKEYSC
- LineNumberType Script::LoadFromFile()
- #else
- LineNumberType Script::LoadFromFile(bool aScriptWasNotspecified)
- #endif
- // Returns the number of non-comment lines that were loaded, or LOADING_FAILED on error.
- {
- mNoHotkeyLabels = true; // Indicate that there are no hotkey labels, since we're (re)loading the entire file.
- mIsReadyToExecute = mAutoExecSectionIsRunning = false;
- if (!mFileSpec || !*mFileSpec) return LOADING_FAILED;
- #ifndef AUTOHOTKEYSC // When not in stand-alone mode, read an external script file.
- DWORD attr = GetFileAttributes(mFileSpec);
- if (attr == MAXDWORD) // File does not exist or lacking the authorization to get its attributes.
- {
- char buf[MAX_PATH + 256];
- if (aScriptWasNotspecified) // v1.0.46.09: Give a more descriptive prompt to help users get started.
- {
- snprintf(buf, sizeof(buf),
- "To help you get started, would you like to create a sample script in the My Documents folder?\n"
- "\n"
- "Press YES to create and display the sample script.\n"
- "Press NO to exit.\n");
- }
- else // Mostly for backward compatibility, also prompt to create if an explicitly specified script doesn't exist.
- snprintf(buf, sizeof(buf), "The script file \"%s\" does not exist. Create it now?", mFileSpec);
- int response = MsgBox(buf, MB_YESNO);
- if (response != IDYES)
- return 0;
- FILE *fp2 = fopen(mFileSpec, "a");
- if (!fp2)
- {
- MsgBox("Could not create file, perhaps because the current directory is read-only"
- " or has insufficient permissions.");
- return LOADING_FAILED;
- }
- fputs(
- "; IMPORTANT INFO ABOUT GETTING STARTED: Lines that start with a\n"
- "; semicolon, such as this one, are comments. They are not executed.\n"
- "\n"
- "; This script has a special filename and path because it is automatically\n"
- "; launched when you run the program directly. Also, any text file whose\n"
- "; name ends in .ahk is associated with the program, which means that it\n"
- "; can be launched simply by double-clicking it. You can have as many .ahk\n"
- "; files as you want, located in any folder. You can also run more than\n"
- "; one ahk file simultaneously and each will get its own tray icon.\n"
- "\n"
- "; SAMPLE HOTKEYS: Below are two sample hotkeys. The first is Win+Z and it\n"
- "; launches a web site in the default browser. The second is Control+Alt+N\n"
- "; and it launches a new Notepad window (or activates an existing one). To\n"
- "; try out these hotkeys, run AutoHotkey again, which will load this file.\n"
- "\n"
- "#z::Run www.autohotkey.com\n"
- "\n"
- "^!n::\n"
- "IfWinExist Untitled - Notepad\n"
- "\tWinActivate\n"
- "else\n"
- "\tRun Notepad\n"
- "return\n"
- "\n"
- "\n"
- "; Note: From now on whenever you run AutoHotkey directly, this script\n"
- "; will be loaded. So feel free to customize it…
Large files files are truncated, but you can click here to view the full file