"Fossies" - the Fresh Open Source Software Archive

Member "AutoHotkey_L-1.1.33.09/source/script_autoit.cpp" (8 May 2021, 81337 Bytes) of package /windows/misc/AutoHotkey_L-1.1.33.09.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "script_autoit.cpp" see the Fossies "Dox" file reference documentation.

    1 ///////////////////////////////////////////////////////////////////////////////
    2 //
    3 // AutoIt
    4 //
    5 // Copyright (C)1999-2006:
    6 //      - Jonathan Bennett <jon@hiddensoft.com>
    7 //      - Others listed at http://www.autoitscript.com/autoit3/docs/credits.htm
    8 //      - Chris Mallett (support@autohotkey.com): various enhancements and
    9 //        adaptation of this file's functions to interface with AutoHotkey.
   10 //
   11 // This program is free software; you can redistribute it and/or modify
   12 // it under the terms of the GNU General Public License as published by
   13 // the Free Software Foundation; either version 2 of the License, or
   14 // (at your option) any later version.
   15 //
   16 ///////////////////////////////////////////////////////////////////////////////
   17 
   18 #include "stdafx.h" // pre-compiled headers
   19 //#include <winsock.h>  // for WSADATA.  This also requires wsock32.lib to be linked in.
   20 #include <winsock2.h>
   21 #include <tlhelp32.h> // For the ProcessExist routines.
   22 #include <wininet.h> // For URLDownloadToFile().
   23 #include "script.h"
   24 #include "globaldata.h" // for g_ErrorLevel and probably other globals.
   25 #include "window.h" // For ControlExist().
   26 #include "application.h" // For SLEEP_WITHOUT_INTERRUPTION and MsgSleep().
   27 
   28 
   29 ResultType Script::DoRunAs(LPTSTR aCommandLine, LPTSTR aWorkingDir, bool aDisplayErrors, WORD aShowWindow
   30     , Var *aOutputVar, PROCESS_INFORMATION &aPI, bool &aSuccess // Output parameters we set for caller, but caller must have initialized aSuccess to false.
   31     , HANDLE &aNewProcess, DWORD &aLastError)                   // Same, but initialize to NULL.
   32 {
   33     typedef BOOL (WINAPI *MyCreateProcessWithLogonW)(
   34         LPCWSTR lpUsername,                 // user's name
   35         LPCWSTR lpDomain,                   // user's domain
   36         LPCWSTR lpPassword,                 // user's password
   37         DWORD dwLogonFlags,                 // logon option
   38         LPCWSTR lpApplicationName,          // executable module name
   39         LPWSTR lpCommandLine,               // command-line string
   40         DWORD dwCreationFlags,              // creation flags
   41         LPVOID lpEnvironment,               // new environment block
   42         LPCWSTR lpCurrentDirectory,         // current directory name
   43         LPSTARTUPINFOW lpStartupInfo,       // startup information
   44         LPPROCESS_INFORMATION lpProcessInfo // process information
   45         );
   46     // Get a handle to the DLL module that contains CreateProcessWithLogonW
   47     HINSTANCE hinstLib = LoadLibrary(_T("advapi32"));
   48     if (!hinstLib)
   49     {
   50         if (aDisplayErrors)
   51             ScriptError(_T("RunAs: Missing advapi32.dll."));
   52         return FAIL;
   53     }
   54     MyCreateProcessWithLogonW lpfnDLLProc = (MyCreateProcessWithLogonW)GetProcAddress(hinstLib, "CreateProcessWithLogonW");
   55     if (!lpfnDLLProc)
   56     {
   57         FreeLibrary(hinstLib);
   58         if (aDisplayErrors)
   59             ScriptError(_T("CreateProcessWithLogonW.")); // Short msg since it probably never happens.
   60         return FAIL;
   61     }
   62     // Set up wide char version that we need for CreateProcessWithLogon
   63     // init structure for running programs (wide char version)
   64     STARTUPINFOW wsi = {0};
   65     wsi.cb          = sizeof(STARTUPINFOW);
   66     wsi.dwFlags     = STARTF_USESHOWWINDOW;
   67     wsi.wShowWindow = aShowWindow;
   68     // The following are left initialized to 0/NULL (initialized earlier above):
   69     //wsi.lpReserved = NULL;
   70     //wsi.lpDesktop = NULL;
   71     //wsi.lpTitle = NULL;
   72     //wsi.cbReserved2 = 0;
   73     //wsi.lpReserved2 = NULL;
   74 
   75 #ifndef UNICODE
   76     // Convert to wide character format:
   77     WCHAR command_line_wide[LINE_SIZE], working_dir_wide[MAX_PATH];
   78     ToWideChar(aCommandLine, command_line_wide, LINE_SIZE); // Dest. size is in wchars, not bytes.
   79     if (aWorkingDir && *aWorkingDir)
   80         ToWideChar(aWorkingDir, working_dir_wide, MAX_PATH); // Dest. size is in wchars, not bytes.
   81     else
   82         *working_dir_wide = 0;  // wide-char terminator.
   83 
   84     if (lpfnDLLProc(mRunAsUser, mRunAsDomain, mRunAsPass, LOGON_WITH_PROFILE, 0
   85         , command_line_wide, 0, 0, *working_dir_wide ? working_dir_wide : NULL, &wsi, &aPI))
   86 #else
   87     if (lpfnDLLProc(mRunAsUser, mRunAsDomain, mRunAsPass, LOGON_WITH_PROFILE, 0
   88         , aCommandLine, 0, 0, aWorkingDir && *aWorkingDir ? aWorkingDir : NULL, &wsi, &aPI))
   89 #endif
   90     {
   91         aSuccess = true;
   92         if (aPI.hThread)
   93             CloseHandle(aPI.hThread); // Required to avoid memory leak.
   94         aNewProcess = aPI.hProcess;
   95         if (aOutputVar)
   96             aOutputVar->Assign(aPI.dwProcessId);
   97     }
   98     else
   99         aLastError = GetLastError(); // Caller will use this to get an error message and set g->LastError if needed.
  100     FreeLibrary(hinstLib);
  101     return OK;
  102 }
  103 
  104 
  105 
  106 VarSizeType BIV_IPAddress(LPTSTR aBuf, LPTSTR aVarName)
  107 {
  108     // aaa.bbb.ccc.ddd = 15, but allow room for larger IP's in the future.
  109     #define IP_ADDRESS_SIZE 32 // The maximum size of any of the strings we return, including terminator.
  110     if (!aBuf)
  111         return IP_ADDRESS_SIZE - 1;  // -1 since we're returning the length of the var's contents, not the size.
  112 
  113     WSADATA wsadata;
  114     if (WSAStartup(MAKEWORD(1, 1), &wsadata)) // Failed (it returns 0 on success).
  115     {
  116         *aBuf = '\0';
  117         return 0;
  118     }
  119 
  120     char host_name[256];
  121     gethostname(host_name, _countof(host_name));
  122     HOSTENT *lpHost = gethostbyname(host_name);
  123 
  124     // au3: How many adapters have we?
  125     int adapter_count = 0;
  126     while (lpHost->h_addr_list[adapter_count])
  127         ++adapter_count;
  128 
  129     int adapter_index = aVarName[11] - '1'; // A_IPAddress[1-4]
  130     if (adapter_index >= adapter_count)
  131         _tcscpy(aBuf, _T("0.0.0.0"));
  132     else
  133     {
  134         IN_ADDR inaddr;
  135         memcpy(&inaddr, lpHost->h_addr_list[adapter_index], 4);
  136         tcslcpy(aBuf, CStringTCharFromCharIfNeeded(inet_ntoa(inaddr)), IP_ADDRESS_SIZE);
  137     }
  138 
  139     WSACleanup();
  140     return (VarSizeType)_tcslen(aBuf);
  141 }
  142 
  143 
  144 
  145 VarSizeType BIV_IsAdmin(LPTSTR aBuf, LPTSTR aVarName)
  146 {
  147     if (!aBuf)
  148         return 1;  // The length of the string "1" or "0".
  149     TCHAR result = '0';  // Default.
  150     SC_HANDLE h = OpenSCManager(NULL, NULL, SC_MANAGER_LOCK);
  151     if (h)
  152     {
  153         SC_LOCK lock = LockServiceDatabase(h);
  154         if (lock)
  155         {
  156             UnlockServiceDatabase(lock);
  157             result = '1'; // Current user is admin.
  158         }
  159         else
  160         {
  161             DWORD lastErr = GetLastError();
  162             if (lastErr == ERROR_SERVICE_DATABASE_LOCKED)
  163                 result = '1'; // Current user is admin.
  164         }
  165         CloseServiceHandle(h);
  166     }
  167     aBuf[0] = result;
  168     aBuf[1] = '\0';
  169     return 1; // Length of aBuf.
  170 }
  171 
  172 
  173 
  174 ResultType Line::PixelGetColor(int aX, int aY, LPTSTR aOptions)
  175 {
  176     if (tcscasestr(aOptions, _T("Slow"))) // New mode for v1.0.43.10.  Takes precedence over Alt mode.
  177         return PixelSearch(aX, aY, aX, aY, 0, 0, aOptions, true); // It takes care of setting ErrorLevel and the output-var.
  178 
  179     Var &output_var = *OUTPUT_VAR;
  180     output_var.Assign(); // Init to empty string regardless of whether we succeed here.
  181 
  182     CoordToScreen(aX, aY, COORD_MODE_PIXEL);
  183     
  184     bool use_alt_mode = tcscasestr(aOptions, _T("Alt")) != NULL; // New mode for v1.0.43.10: Two users reported that CreateDC works better in certain windows such as SciTE, at least one some systems.
  185     HDC hdc = use_alt_mode ? CreateDC(_T("DISPLAY"), NULL, NULL, NULL) : GetDC(NULL);
  186     if (!hdc)
  187         return SetErrorLevelOrThrow();
  188 
  189     // Assign the value as an 32-bit int to match Window Spy reports color values.
  190     // Update for v1.0.21: Assigning in hex format seems much better, since it's easy to
  191     // look at a hex BGR value to get some idea of the hue.  In addition, the result
  192     // is zero padded to make it easier to convert to RGB and more consistent in
  193     // appearance:
  194     COLORREF color = GetPixel(hdc, aX, aY);
  195     if (use_alt_mode)
  196         DeleteDC(hdc);
  197     else
  198         ReleaseDC(NULL, hdc);
  199 
  200     TCHAR buf[32];
  201     _stprintf(buf, _T("0x%06X"), tcscasestr(aOptions, _T("RGB")) ? bgr_to_rgb(color) : color);
  202     g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  203     return output_var.Assign(buf);
  204 }
  205 
  206 
  207 
  208 ResultType Line::SplashTextOn(int aWidth, int aHeight, LPTSTR aTitle, LPTSTR aText)
  209 {
  210     // Add some caption and frame size to window:
  211     aWidth += GetSystemMetrics(SM_CXFIXEDFRAME) * 2;
  212     int min_height = GetSystemMetrics(SM_CYCAPTION) + (GetSystemMetrics(SM_CXFIXEDFRAME) * 2);
  213     // This method seems more friendly than setting aHeight = min_height when aHeight < min_height.
  214     aHeight += min_height;
  215 
  216     POINT pt = CenterWindow(aWidth, aHeight); // Determine how to center the window in the region that excludes the task bar.
  217 
  218     // My: Probably not too much overhead to do this, though it probably would perform better to resize and
  219     // "re-text" the existing window rather than recreating it like this:
  220     DESTROY_SPLASH
  221 
  222     // Doesn't seem necessary to have it owned by the main window, but neither
  223     // does doing so seem to cause any harm.  Feels safer to have it be
  224     // an independent window.  Update: Must make it owned by the parent window
  225     // otherwise it will get its own task-bar icon, which is usually undesirable.
  226     // In addition, making it an owned window should automatically cause it to be
  227     // destroyed when it's parent window is destroyed:
  228     g_hWndSplash = CreateWindowEx(WS_EX_TOPMOST, WINDOW_CLASS_SPLASH, aTitle, WS_DISABLED|WS_POPUP|WS_CAPTION
  229         , pt.x, pt.y, aWidth, aHeight, g_hWnd, (HMENU)NULL, g_hInstance, NULL);
  230 
  231     RECT rect;
  232     GetClientRect(g_hWndSplash, &rect); // get the client size
  233 
  234     // CREATE static label full size of client area.
  235     HWND static_win = CreateWindowEx(0, _T("static"), aText, WS_CHILD|WS_VISIBLE|SS_CENTER
  236         , 0, 0, rect.right - rect.left, rect.bottom - rect.top, g_hWndSplash, (HMENU)NULL, g_hInstance, NULL);
  237 
  238     if (!g_hFontSplash)
  239     {
  240         TCHAR default_font_name[65];
  241         int CyPixels, nSize = 12, nWeight = FW_NORMAL;
  242         HDC hdc = CreateDC(_T("DISPLAY"), NULL, NULL, NULL);
  243         if (FontExist(hdc, _T("Segoe UI"))) // Use a more appealing font under Windows Vista or later (Segoe UI).
  244         {
  245             nSize = 11;
  246             _tcscpy(default_font_name, _T("Segoe UI"));
  247         }
  248         else
  249         {
  250             SelectObject(hdc, (HFONT)GetStockObject(DEFAULT_GUI_FONT));     // Get Default Font Name
  251             GetTextFace(hdc, _countof(default_font_name) - 1, default_font_name); // -1 just in case, like AutoIt3.
  252         }
  253         CyPixels = GetDeviceCaps(hdc, LOGPIXELSY);          // For Some Font Size Math
  254         DeleteDC(hdc);
  255         //strcpy(default_font_name,vParams[7].szValue());   // Font Name
  256         //nSize = vParams[8].nValue();      // Font Size
  257         //if ( vParams[9].nValue() >= 0 && vParams[9].nValue() <= 1000 )
  258         //  nWeight = vParams[9].nValue();          // Font Weight
  259         g_hFontSplash = CreateFont(0-(nSize*CyPixels)/72,0,0,0,nWeight,0,0,0,DEFAULT_CHARSET,
  260             OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,FF_DONTCARE,default_font_name); // Create Font
  261         // The font is deleted when by g_script's destructor.
  262     }
  263 
  264     SendMessage(static_win, WM_SETFONT, (WPARAM)g_hFontSplash, MAKELPARAM(TRUE, 0));    // Do Font
  265     ShowWindow(g_hWndSplash, SW_SHOWNOACTIVATE);                // Show the Splash
  266     // Doesn't help with the brief delay in updating the window that happens when
  267     // something like URLDownloadToFile is used immediately after SplashTextOn:
  268     //InvalidateRect(g_hWndSplash, NULL, TRUE);
  269     // But this does, but for now it seems unnecessary since the user can always do
  270     // a manual sleep in the extremely rare cases this ever happens (even when it does
  271     // happen, the window updates eventually, after the download starts, at least on
  272     // my system.  Update: Might as well do it since it's a little nicer this way
  273     // (the text appears more quickly when the command after the splash is something
  274     // that might keep our thread tied up and unable to check messages).
  275     SLEEP_WITHOUT_INTERRUPTION(-1)
  276     // UpdateWindow() would probably achieve the same effect as the above, but it feels safer to do
  277     // the above because it ensures that our message queue is empty prior to returning to our caller.
  278     return OK;
  279 }
  280 
  281 
  282 
  283 ResultType Line::WinMenuSelectItem(LPTSTR aTitle, LPTSTR aText, LPTSTR aMenu1, LPTSTR aMenu2
  284     , LPTSTR aMenu3, LPTSTR aMenu4, LPTSTR aMenu5, LPTSTR aMenu6, LPTSTR aMenu7
  285     , LPTSTR aExcludeTitle, LPTSTR aExcludeText)
  286 {
  287     // Set up a temporary array make it easier to traverse nested menus & submenus
  288     // in a loop.  Also add a NULL at the end to simplify the loop a little:
  289     LPTSTR menu_param[] = {aMenu1, aMenu2, aMenu3, aMenu4, aMenu5, aMenu6, aMenu7, NULL};
  290 
  291     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  292     if (!target_window)
  293         goto error;
  294 
  295     int first_menu_param = 0;
  296     UINT message = WM_COMMAND;
  297     HMENU hMenu;
  298     if (!_tcsicmp(aMenu1, _T("0&")))
  299     {
  300         hMenu = GetSystemMenu(target_window, FALSE);
  301         first_menu_param = 1;
  302         message = WM_SYSCOMMAND;
  303     }
  304     else
  305         hMenu = GetMenu(target_window);
  306     if (!hMenu) // Window has no menu bar.
  307         goto error;
  308 
  309     int menu_item_count = GetMenuItemCount(hMenu);
  310     if (menu_item_count < 1) // Menu bar has no menus.
  311         goto error;
  312     
  313 #define MENU_ITEM_IS_SUBMENU 0xFFFFFFFF
  314 #define UPDATE_MENU_VARS(menu_pos) \
  315 menu_id = GetMenuItemID(hMenu, menu_pos);\
  316 if (menu_id == MENU_ITEM_IS_SUBMENU)\
  317     menu_item_count = GetMenuItemCount(hMenu = GetSubMenu(hMenu, menu_pos));\
  318 else\
  319 {\
  320     menu_item_count = 0;\
  321     hMenu = NULL;\
  322 }
  323 
  324     UINT menu_id = MENU_ITEM_IS_SUBMENU;
  325     TCHAR menu_text[1024];
  326     bool match_found;
  327     size_t this_menu_param_length, menu_text_length;
  328     int pos, target_menu_pos;
  329     LPTSTR this_menu_param;
  330 
  331     for (int i = first_menu_param; ; ++i)
  332     {
  333         this_menu_param = menu_param[i]; // For performance and convenience.
  334         if (!(this_menu_param && *this_menu_param))
  335             break;
  336         if (!hMenu)  // The nesting of submenus ended prior to the end of the list of menu search terms.
  337             goto error;
  338 
  339         this_menu_param_length = _tcslen(this_menu_param);
  340         target_menu_pos = (this_menu_param[this_menu_param_length - 1] == '&') ? ATOI(this_menu_param) - 1 : -1;
  341         if (target_menu_pos > -1)
  342         {
  343             if (target_menu_pos >= menu_item_count)  // Invalid menu position (doesn't exist).
  344                 goto error;
  345             UPDATE_MENU_VARS(target_menu_pos)
  346         }
  347         else // Searching by text rather than numerical position.
  348         {
  349             for (match_found = false, pos = 0; pos < menu_item_count; ++pos)
  350             {
  351                 menu_text_length = GetMenuString(hMenu, pos, menu_text, _countof(menu_text) - 1, MF_BYPOSITION);
  352                 // v1.0.43.03: It's debatable, but it seems best to support locale's case insensitivity for
  353                 // menu items, since menu names tend to adapt to the user's locale.  By contrast, things
  354                 // like process names (in the Process command) do not tend to change, so it seems best to
  355                 // have them continue to use stricmp(): 1) avoids breaking existing scripts; 2) provides
  356                 // consistent behavior across multiple locales; 3) performance.
  357                 match_found = !lstrcmpni(menu_text  // This call is basically a strnicmp() that obeys locale.
  358                     , menu_text_length > this_menu_param_length ? this_menu_param_length : menu_text_length
  359                     , this_menu_param, this_menu_param_length);
  360                 //match_found = strcasestr(menu_text, this_menu_param);
  361                 TCHAR *cpamp;
  362                 if (!match_found && (cpamp = _tcschr(menu_text, '&')))
  363                 {
  364                     // Try again to find a match, this time without the ampersands used to indicate a menu item's
  365                     // shortcut key.  One might assume that only the first & needs to be removed, but the actual
  366                     // behaviour confirmed on Windows 10.0.19041 was:
  367                     //  - Only every second & in a series of consecutive &&s was kept.
  368                     //  - Only the *first* &-letter was usable as a shortcut key.
  369                     //  - Only the *last* & caused an underline.
  370                     for (TCHAR *cplit = cpamp; ; ++cpamp, ++cplit)
  371                     {
  372                         if (*cpamp == '&')
  373                             ++cpamp; // Skip this '&' but copy the next character unconditionally, even if it is '&'.
  374                         *cplit = *cpamp;
  375                         if (!*cpamp)
  376                             break; // Only after copying, so menu_text is null-terminated at the correct position (cpw).
  377                     }
  378                     menu_text_length = _tcslen(menu_text);
  379                     match_found = !lstrcmpni(menu_text  // This call is basically a strnicmp() that obeys locale.
  380                         , menu_text_length > this_menu_param_length ? this_menu_param_length : menu_text_length
  381                         , this_menu_param, this_menu_param_length);
  382                     //match_found = strcasestr(menu_text, this_menu_param);
  383                 }
  384                 if (match_found)
  385                 {
  386                     UPDATE_MENU_VARS(pos)
  387                     break;
  388                 }
  389             } // inner for()
  390             if (!match_found) // The search hierarchy (nested menus) specified in the params could not be found.
  391                 goto error;
  392         } // else
  393     } // outer for()
  394 
  395     // This would happen if the outer loop above had zero iterations due to aMenu1 being NULL or blank,
  396     // or if the caller specified a submenu as the target (which doesn't seem valid since an app would
  397     // next expect to ever receive a message for a submenu?):
  398     if (menu_id == MENU_ITEM_IS_SUBMENU)
  399         goto error;
  400 
  401     // Since the above didn't return, the specified search hierarchy was completely found.
  402     PostMessage(target_window, message, (WPARAM)menu_id, 0);
  403     return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  404 
  405 error:
  406     return SetErrorLevelOrThrow();
  407 }
  408 
  409 
  410 
  411 ResultType Line::Control(LPTSTR aCmd, LPTSTR aValue, LPTSTR aControl, LPTSTR aTitle, LPTSTR aText
  412     , LPTSTR aExcludeTitle, LPTSTR aExcludeText)
  413 // ATTACH_THREAD_INPUT has been tested to see if they help any of these work with controls
  414 // in MSIE (whose Internet Explorer_TridentCmboBx2 does not respond to "Control Choose" but
  415 // does respond to "Control Focus").  But it didn't help.
  416 {
  417     ControlCmds control_cmd = ConvertControlCmd(aCmd);
  418     // Since command names are validated at load-time, this only happens if the command name
  419     // was contained in a variable reference.  Since that is very rare, just set ErrorLevel
  420     // and return:
  421     if (control_cmd == CONTROL_CMD_INVALID)
  422         goto error;
  423 
  424     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  425     if (!target_window)
  426         goto error;
  427     HWND control_window = ControlExist(target_window, aControl); // This can return target_window itself for cases such as ahk_id %ControlHWND%.
  428     if (!control_window)
  429         goto error;
  430 
  431     HWND immediate_parent;  // Possibly not the same as target_window since controls can themselves have children.
  432     int control_id, control_index;
  433     DWORD_PTR dwResult, new_button_state;
  434     UINT msg, x_msg, y_msg;
  435     RECT rect;
  436     LPARAM lparam;
  437     vk_type vk;
  438     int key_count;
  439     TCHAR temp_buf[32];
  440 
  441     switch(control_cmd)
  442     {
  443     case CONTROL_CMD_CHECK: // au3: Must be a Button
  444     case CONTROL_CMD_UNCHECK:
  445     { // Need braces for ATTACH_THREAD_INPUT macro.
  446         new_button_state = (control_cmd == CONTROL_CMD_CHECK) ? BST_CHECKED : BST_UNCHECKED;
  447         if (!SendMessageTimeout(control_window, BM_GETCHECK, 0, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  448             goto error;
  449         if (dwResult == new_button_state) // It's already in the right state, so don't press it.
  450             break;
  451         // MSDN docs for BM_CLICK (and au3 author says it applies to this situation also):
  452         // "If the button is in a dialog box and the dialog box is not active, the BM_CLICK message
  453         // might fail. To ensure success in this situation, call the SetActiveWindow function to activate
  454         // the dialog box before sending the BM_CLICK message to the button."
  455         ATTACH_THREAD_INPUT
  456         SetActiveWindow(target_window == control_window ? GetNonChildParent(control_window) : target_window); // v1.0.44.13: Fixed to allow for the fact that target_window might be the control itself (e.g. via ahk_id %ControlHWND%).
  457         if (!GetWindowRect(control_window, &rect))  // au3: Code to primary click the centre of the control
  458             rect.bottom = rect.left = rect.right = rect.top = 0;
  459         lparam = MAKELPARAM((rect.right - rect.left) / 2, (rect.bottom - rect.top) / 2);
  460         PostMessage(control_window, WM_LBUTTONDOWN, MK_LBUTTON, lparam);
  461         PostMessage(control_window, WM_LBUTTONUP, 0, lparam);
  462         DETACH_THREAD_INPUT
  463         break;
  464     }
  465 
  466     case CONTROL_CMD_ENABLE:
  467         EnableWindow(control_window, TRUE);
  468         break;
  469 
  470     case CONTROL_CMD_DISABLE:
  471         EnableWindow(control_window, FALSE);
  472         break;
  473 
  474     case CONTROL_CMD_SHOW:
  475         ShowWindow(control_window, SW_SHOWNOACTIVATE); // SW_SHOWNOACTIVATE has been seen in some example code for this purpose.
  476         break;
  477 
  478     case CONTROL_CMD_HIDE:
  479         ShowWindow(control_window, SW_HIDE);
  480         break;
  481 
  482     case CONTROL_CMD_STYLE:
  483     case CONTROL_CMD_EXSTYLE:
  484     {
  485         if (!*aValue)
  486             return OK; // Seems best not to treat an explicit blank as zero.  Let ErrorLevel tell the story. 
  487         int style_index = (control_cmd == CONTROL_CMD_STYLE) ? GWL_STYLE : GWL_EXSTYLE;
  488         DWORD new_style, orig_style = GetWindowLong(control_window, style_index);
  489         // +/-/^ are used instead of |&^ because the latter is confusing, namely that & really means &=~style, etc.
  490         if (!_tcschr(_T("+-^"), *aValue))  // | and & are used instead of +/- to allow +/- to have their native function.
  491             new_style = ATOU(aValue); // No prefix, so this new style will entirely replace the current style.
  492         else
  493         {
  494             ++aValue; // Won't work combined with next line, due to next line being a macro that uses the arg twice.
  495             DWORD style_change = ATOU(aValue);
  496             switch(aValue[-1])
  497             {
  498             case '+': new_style = orig_style | style_change; break;
  499             case '-': new_style = orig_style & ~style_change; break;
  500             case '^': new_style = orig_style ^ style_change; break;
  501             }
  502         }
  503         if (new_style == orig_style) // v1.0.45.04: Ask for an unnecessary change (i.e. one that is already in effect) should not be considered an error.
  504             return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  505         // Currently, BM_SETSTYLE is not done when GetClassName() says that the control is a button/checkbox/groupbox.
  506         // This is because the docs for BM_SETSTYLE don't contain much, if anything, that anyone would ever
  507         // want to change.
  508         SetLastError(0); // Prior to SetWindowLong(), as recommended by MSDN.
  509         if (SetWindowLong(control_window, style_index, new_style) || !GetLastError()) // This is the precise way to detect success according to MSDN.
  510         {
  511             // Even if it indicated success, sometimes it failed anyway.  Find out for sure:
  512             if (GetWindowLong(control_window, style_index) != orig_style) // Even a partial change counts as a success.
  513             {
  514                 InvalidateRect(control_window, NULL, TRUE); // Quite a few styles require this to become visibly manifest.
  515                 return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  516             }
  517         }
  518         goto error; // As documented, DoControlDelay is not done for these.
  519     }
  520 
  521     case CONTROL_CMD_SHOWDROPDOWN:
  522     case CONTROL_CMD_HIDEDROPDOWN:
  523         // CB_SHOWDROPDOWN: Although the return value (dwResult) is always TRUE, SendMessageTimeout()
  524         // will return failure if it times out:
  525         if (!SendMessageTimeout(control_window, CB_SHOWDROPDOWN
  526             , (WPARAM)(control_cmd == CONTROL_CMD_SHOWDROPDOWN)
  527             , 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  528             goto error;
  529         break;
  530 
  531     case CONTROL_CMD_TABLEFT:
  532     case CONTROL_CMD_TABRIGHT: // must be a Tab Control
  533         key_count = *aValue ? ATOI(aValue) : 1;
  534         vk = (control_cmd == CONTROL_CMD_TABLEFT) ? VK_LEFT : VK_RIGHT;
  535         lparam = (LPARAM)(vk_to_sc(vk) << 16);
  536         for (int i = 0; i < key_count; ++i)
  537         {
  538             // DoControlDelay isn't done for every iteration because it seems likely that
  539             // the Sleep(0) will take care of things.
  540             PostMessage(control_window, WM_KEYDOWN, vk, lparam | 0x00000001);
  541             SLEEP_WITHOUT_INTERRUPTION(0); // Au3 uses a Sleep(0).
  542             PostMessage(control_window, WM_KEYUP, vk, lparam | 0xC0000001);
  543         }
  544         break;
  545 
  546     case CONTROL_CMD_ADD:
  547         if (!*aControl) // Fix for v1.0.46.11: If aControl is blank, the control ID came in via a WinTitle of "ahk_id xxx".
  548         {
  549             GetClassName(control_window, temp_buf, _countof(temp_buf));
  550             aControl = temp_buf;
  551         }
  552         if (tcscasestr(aControl, _T("Combo"))) // v1.0.42: Changed to strcasestr vs. !strnicmp for TListBox/TComboBox.
  553             msg = CB_ADDSTRING;
  554         else if (tcscasestr(aControl, _T("List")))
  555             msg = LB_ADDSTRING;
  556         else
  557             goto error;  // Must be ComboBox or ListBox.
  558         if (!SendMessageTimeout(control_window, msg, 0, (LPARAM)aValue, SMTO_ABORTIFHUNG, 2000, &dwResult))
  559             goto error;
  560         if (dwResult == CB_ERR || dwResult == CB_ERRSPACE) // General error or insufficient space to store it.
  561             // CB_ERR == LB_ERR
  562             goto error;
  563         break;
  564 
  565     case CONTROL_CMD_DELETE:
  566         if (!*aValue)
  567             goto error;
  568         control_index = ATOI(aValue) - 1;
  569         if (control_index < 0)
  570             goto error;
  571         if (!*aControl) // Fix for v1.0.46.11: If aControl is blank, the control ID came in via a WinTitle of "ahk_id xxx".
  572         {
  573             GetClassName(control_window, temp_buf, _countof(temp_buf));
  574             aControl = temp_buf;
  575         }
  576         if (tcscasestr(aControl, _T("Combo"))) // v1.0.42: Changed to strcasestr vs. strnicmp for TListBox/TComboBox.
  577             msg = CB_DELETESTRING;
  578         else if (tcscasestr(aControl, _T("List")))
  579             msg = LB_DELETESTRING;
  580         else
  581             goto error;  // Must be ComboBox or ListBox.
  582         if (!SendMessageTimeout(control_window, msg, (WPARAM)control_index, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  583             goto error;
  584         if (dwResult == CB_ERR)  // CB_ERR == LB_ERR
  585             goto error;
  586         break;
  587 
  588     case CONTROL_CMD_CHOOSE:
  589         if (!*aValue)
  590             goto error;
  591         control_index = ATOI(aValue) - 1;
  592         if (control_index < 0)
  593             goto error;
  594         if (!*aControl) // Fix for v1.0.46.11: If aControl is blank, the control ID came in via a WinTitle of "ahk_id xxx".
  595         {
  596             GetClassName(control_window, temp_buf, _countof(temp_buf));
  597             aControl = temp_buf;
  598         }
  599         if (tcscasestr(aControl, _T("Combo"))) // v1.0.42: Changed to strcasestr vs. strnicmp for TListBox/TComboBox.
  600         {
  601             msg = CB_SETCURSEL;
  602             x_msg = CBN_SELCHANGE;
  603             y_msg = CBN_SELENDOK;
  604         }
  605         else if (tcscasestr(aControl, _T("List")))
  606         {
  607             if (GetWindowLong(control_window, GWL_STYLE) & (LBS_EXTENDEDSEL|LBS_MULTIPLESEL))
  608                 msg = LB_SETSEL;
  609             else // single-select listbox
  610                 msg = LB_SETCURSEL;
  611             x_msg = LBN_SELCHANGE;
  612             y_msg = LBN_DBLCLK;
  613         }
  614         else
  615             goto error;
  616         if (msg == LB_SETSEL) // Multi-select, so use the cumulative method.
  617         {
  618             if (!SendMessageTimeout(control_window, msg, TRUE, control_index, SMTO_ABORTIFHUNG, 2000, &dwResult))
  619                 goto error;
  620         }
  621         else // ComboBox or single-select ListBox.
  622             if (!SendMessageTimeout(control_window, msg, control_index, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  623                 goto error;
  624         if (dwResult == CB_ERR)  // CB_ERR == LB_ERR
  625             goto error;
  626         goto notify_parent;
  627 
  628     case CONTROL_CMD_CHOOSESTRING:
  629         if (!*aControl) // Fix for v1.0.46.11: If aControl is blank, the control ID came in via a WinTitle of "ahk_id xxx".
  630         {
  631             GetClassName(control_window, temp_buf, _countof(temp_buf));
  632             aControl = temp_buf;
  633         }
  634         if (tcscasestr(aControl, _T("Combo"))) // v1.0.42: Changed to strcasestr vs. strnicmp for TListBox/TComboBox.
  635         {
  636             msg = CB_SELECTSTRING;
  637             x_msg = CBN_SELCHANGE;
  638             y_msg = CBN_SELENDOK;
  639         }
  640         else if (tcscasestr(aControl, _T("List")))
  641         {
  642             if (GetWindowLong(control_window, GWL_STYLE) & (LBS_EXTENDEDSEL|LBS_MULTIPLESEL))
  643                 msg = LB_FINDSTRING;
  644             else // single-select listbox
  645                 msg = LB_SELECTSTRING;
  646             x_msg = LBN_SELCHANGE;
  647             y_msg = LBN_DBLCLK;
  648         }
  649         else
  650             goto error;  // Must be ComboBox or ListBox.
  651         if (msg == LB_FINDSTRING) // Multi-select ListBox (LB_SELECTSTRING is not supported by these).
  652         {
  653             DWORD_PTR item_index;
  654             if (!SendMessageTimeout(control_window, msg, -1, (LPARAM)aValue, SMTO_ABORTIFHUNG, 2000, &item_index)
  655                 || item_index == LB_ERR
  656                 || !SendMessageTimeout(control_window, LB_SETSEL, TRUE, item_index, SMTO_ABORTIFHUNG, 2000, &dwResult)
  657                 || dwResult == LB_ERR) // Relies on short-circuit boolean.
  658                 goto error;
  659         }
  660         else // ComboBox or single-select ListBox.
  661             if (!SendMessageTimeout(control_window, msg, -1, (LPARAM)aValue, SMTO_ABORTIFHUNG, 2000, &dwResult)
  662                 || dwResult == CB_ERR) // CB_ERR == LB_ERR
  663                 goto error;
  664     notify_parent:
  665         if (   !(immediate_parent = GetParent(control_window))   )
  666             goto error;
  667         SetLastError(0); // Must be done to differentiate between success and failure when control has ID 0.
  668         control_id = GetDlgCtrlID(control_window);
  669         if (!control_id && GetLastError()) // Both conditions must be checked (see above).
  670             goto error; // Avoid sending the notification in case some other control has ID 0.
  671         // Proceed even if control_id == 0, since some applications are known to
  672         // utilize the notification in that case (e.g. Notepad's Save As dialog).
  673         if (!SendMessageTimeout(immediate_parent, WM_COMMAND, (WPARAM)MAKELONG(control_id, x_msg)
  674             , (LPARAM)control_window, SMTO_ABORTIFHUNG, 2000, &dwResult))
  675             goto error;
  676         if (!SendMessageTimeout(immediate_parent, WM_COMMAND, (WPARAM)MAKELONG(control_id, y_msg)
  677             , (LPARAM)control_window, SMTO_ABORTIFHUNG, 2000, &dwResult))
  678             goto error;
  679         // Otherwise break and do the end-function processing.
  680         break;
  681 
  682     case CONTROL_CMD_EDITPASTE:
  683         if (!SendMessageTimeout(control_window, EM_REPLACESEL, TRUE, (LPARAM)aValue, SMTO_ABORTIFHUNG, 2000, &dwResult))
  684             goto error;
  685         // Note: dwResult is not used by EM_REPLACESEL since it doesn't return a value.
  686         break;
  687     } // switch()
  688 
  689     DoControlDelay;  // Seems safest to do this for all of these commands.
  690     return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  691 
  692 error:
  693     return SetErrorLevelOrThrow();
  694 }
  695 
  696 
  697 
  698 ResultType Line::ControlGet(LPTSTR aCmd, LPTSTR aValue, LPTSTR aControl, LPTSTR aTitle, LPTSTR aText
  699     , LPTSTR aExcludeTitle, LPTSTR aExcludeText)
  700 {
  701     Var &output_var = *OUTPUT_VAR;
  702     ControlGetCmds control_cmd = ConvertControlGetCmd(aCmd);
  703     // Since command names are validated at load-time, this only happens if the command name
  704     // was contained in a variable reference.  Since that is very rare, just set ErrorLevel
  705     // and return:
  706     if (control_cmd == CONTROLGET_CMD_INVALID)
  707         goto error;
  708 
  709     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  710     if (!target_window)
  711         goto error;
  712     HWND control_window = ControlExist(target_window, aControl); // This can return target_window itself for cases such as ahk_id %ControlHWND%.
  713     if (!control_window)
  714         goto error;
  715 
  716     DWORD_PTR dwResult, index, length, item_length, u, item_count;
  717     DWORD start, end;
  718     UINT msg, x_msg, y_msg;
  719     int control_index;
  720     TCHAR *cp, *dyn_buf, temp_buf[32];
  721 
  722     switch(control_cmd)
  723     {
  724     case CONTROLGET_CMD_CHECKED: //Must be a Button
  725         if (!SendMessageTimeout(control_window, BM_GETCHECK, 0, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  726             goto error;
  727         output_var.Assign(dwResult == BST_CHECKED ? _T("1") : _T("0"));
  728         break;
  729 
  730     case CONTROLGET_CMD_ENABLED:
  731         output_var.Assign(IsWindowEnabled(control_window) ? _T("1") : _T("0"));
  732         break;
  733 
  734     case CONTROLGET_CMD_VISIBLE:
  735         output_var.Assign(IsWindowVisible(control_window) ? _T("1") : _T("0"));
  736         break;
  737 
  738     case CONTROLGET_CMD_TAB: // must be a Tab Control
  739         if (!SendMessageTimeout(control_window, TCM_GETCURSEL, 0, 0, SMTO_ABORTIFHUNG, 2000, &index) || index == -1) // Relies on short-circuit boolean order.
  740             goto error;
  741         output_var.Assign(index + 1);
  742         break;
  743 
  744     case CONTROLGET_CMD_FINDSTRING:
  745         if (!*aControl) // Fix for v1.0.46.11: If aControl is blank, the control ID came in via a WinTitle of "ahk_id xxx".
  746         {
  747             GetClassName(control_window, temp_buf, _countof(temp_buf));
  748             aControl = temp_buf;
  749         }
  750         if (tcscasestr(aControl, _T("Combo"))) // v1.0.42: Changed to strcasestr vs. strnicmp for TListBox/TComboBox.
  751             msg = CB_FINDSTRINGEXACT;
  752         else if (tcscasestr(aControl, _T("List")))
  753             msg = LB_FINDSTRINGEXACT;
  754         else // Must be ComboBox or ListBox
  755             goto error;
  756         if (!SendMessageTimeout(control_window, msg, -1, (LPARAM)aValue, SMTO_ABORTIFHUNG, 2000, &index)
  757             || index == CB_ERR) // CB_ERR == LB_ERR
  758             goto error;
  759         output_var.Assign(index + 1);
  760         break;
  761 
  762     case CONTROLGET_CMD_CHOICE:
  763         if (!*aControl) // Fix for v1.0.46.11: If aControl is blank, the control ID came in via a WinTitle of "ahk_id xxx".
  764         {
  765             GetClassName(control_window, temp_buf, _countof(temp_buf));
  766             aControl = temp_buf;
  767         }
  768         if (tcscasestr(aControl, _T("Combo"))) // v1.0.42: Changed to strcasestr vs. strnicmp for TListBox/TComboBox.
  769         {
  770             msg = CB_GETCURSEL;
  771             x_msg = CB_GETLBTEXTLEN;
  772             y_msg = CB_GETLBTEXT;
  773         }
  774         else if (tcscasestr(aControl, _T("List")))
  775         {
  776             msg = LB_GETCURSEL;
  777             x_msg = LB_GETTEXTLEN;
  778             y_msg = LB_GETTEXT;
  779         }
  780         else // Must be ComboBox or ListBox
  781             return output_var.Assign();  // Let ErrorLevel tell the story.
  782         if (!SendMessageTimeout(control_window, msg, 0, 0, SMTO_ABORTIFHUNG, 2000, &index)
  783             || index == CB_ERR  // CB_ERR == LB_ERR.  There is no selection (or very rarely, some other type of problem).
  784             || !SendMessageTimeout(control_window, x_msg, (WPARAM)index, 0, SMTO_ABORTIFHUNG, 2000, &length)
  785             || length == CB_ERR)  // CB_ERR == LB_ERR
  786             goto error; // Above relies on short-circuit boolean order.
  787         // In unusual cases, MSDN says the indicated length might be longer than it actually winds up
  788         // being when the item's text is retrieved.  This should be harmless, since there are many
  789         // other precedents where a variable is sized to something larger than it winds up carrying.
  790         // Set up the var, enlarging it if necessary.  If the output_var is of type VAR_CLIPBOARD,
  791         // this call will set up the clipboard for writing:
  792         if (output_var.AssignString(NULL, (VarSizeType)length) != OK) // It already displayed the error.
  793             return FAIL;
  794         if (!SendMessageTimeout(control_window, y_msg, (WPARAM)index, (LPARAM)output_var.Contents()
  795             , SMTO_ABORTIFHUNG, 2000, &length)
  796             || length == CB_ERR) // Probably impossible given the way it was called above.  Also, CB_ERR == LB_ERR. Relies on short-circuit boolean order.
  797         {
  798             output_var.Close();
  799             goto error;
  800         }
  801         output_var.Close(); // Must be called after Assign(NULL, ...) or when Contents() has been altered because it updates the variable's attributes and properly handles VAR_CLIPBOARD.
  802         output_var.SetCharLength(length);  // Update to actual vs. estimated length.
  803         break;
  804 
  805     case CONTROLGET_CMD_LIST:
  806         if (!*aControl) // Fix for v1.0.46.11: If aControl is blank, the control ID came in via a WinTitle of "ahk_id xxx".
  807         {
  808             GetClassName(control_window, temp_buf, _countof(temp_buf));
  809             aControl = temp_buf;
  810         }
  811         //if (!_tcsnicmp(aControl, _T("SysListView32"), 13)) // Tried strcasestr(aControl, "ListView") to get it to work with IZArc's Delphi TListView1, but none of the modes or options worked.
  812         if (tcscasestr(aControl, _T("SysListView32"))) // Some users said this works with "WindowsForms10.SysListView32"
  813             return ControlGetListView(output_var, control_window, aValue); // It will also set ErrorLevel to "success" if successful.
  814         // This is done here as the special LIST sub-command rather than just being built into
  815         // ControlGetText because ControlGetText already has a function for ComboBoxes: it fetches
  816         // the current selection.  Although ListBox does not have such a function, it seem best
  817         // to consolidate both methods here.
  818         if (tcscasestr(aControl, _T("Combo"))) // v1.0.42: Changed to strcasestr vs. strnicmp for TListBox/TComboBox.
  819         {
  820             msg = CB_GETCOUNT;
  821             x_msg = CB_GETLBTEXTLEN;
  822             y_msg = CB_GETLBTEXT;
  823         }
  824         else if (tcscasestr(aControl, _T("List")))
  825         {
  826             msg = LB_GETCOUNT;
  827             x_msg = LB_GETTEXTLEN;
  828             y_msg = LB_GETTEXT;
  829         }
  830         else // Must be ComboBox or ListBox
  831             goto error;
  832         if (!(SendMessageTimeout(control_window, msg, 0, 0, SMTO_ABORTIFHUNG, 5000, &item_count))
  833             || item_count < 1) // No items in ListBox/ComboBox or there was a problem getting the count.
  834             goto error;
  835         // Calculate the length of delimited list of items.  Length is initialized to provide enough
  836         // room for each item's delimiter (the last item does not have a delimiter).
  837         for (length = item_count - 1, u = 0; u < item_count; ++u)
  838         {
  839             if (!SendMessageTimeout(control_window, x_msg, u, 0, SMTO_ABORTIFHUNG, 5000, &item_length)
  840                 || item_length == LB_ERR) // Note that item_length is legitimately zero for a blank item in the list.
  841                 return output_var.Assign();  // Let ErrorLevel tell the story.
  842             length += item_length;
  843         }
  844         // In unusual cases, MSDN says the indicated length might be longer than it actually winds up
  845         // being when the item's text is retrieved.  This should be harmless, since there are many
  846         // other precedents where a variable is sized to something larger than it winds up carrying.
  847         // Set up the var, enlarging it if necessary.  If the output_var is of type VAR_CLIPBOARD,
  848         // this call will set up the clipboard for writing:
  849         if (output_var.AssignString(NULL, (VarSizeType)length, true, true) != OK)
  850             return FAIL;  // It already displayed the error.
  851         for (cp = output_var.Contents(), length = item_count - 1, u = 0; u < item_count; ++u)
  852         {
  853             if (SendMessageTimeout(control_window, y_msg, (WPARAM)u, (LPARAM)cp, SMTO_ABORTIFHUNG, 5000, &item_length)
  854                 && item_length != LB_ERR)
  855             {
  856                 length += item_length; // Accumulate actual vs. estimated length.
  857                 cp += item_length;  // Point it to the terminator in preparation for the next write.
  858             }
  859             //else do nothing, just consider this to be a blank item so that the process can continue.
  860             if (u < item_count - 1)
  861                 *cp++ = '\n'; // Add delimiter after each item except the last (helps parsing loop).
  862             // Above: In this case, seems better to use \n rather than pipe as default delimiter in case
  863             // the listbox/combobox contains any real pipes.
  864         }
  865         output_var.Close(); // Must be called after Assign(NULL, ...) or when Contents() has been altered because it updates the variable's attributes and properly handles VAR_CLIPBOARD.
  866         output_var.SetCharLength(length);  // Update it to the actual length, which can vary from the estimate.
  867         break;
  868 
  869     case CONTROLGET_CMD_LINECOUNT:  //Must be an Edit
  870         // MSDN: "If the control has no text, the return value is 1. The return value will never be less than 1."
  871         if (!SendMessageTimeout(control_window, EM_GETLINECOUNT, 0, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  872             goto error;
  873         output_var.Assign(dwResult);
  874         break;
  875 
  876     case CONTROLGET_CMD_CURRENTLINE:
  877         if (!SendMessageTimeout(control_window, EM_LINEFROMCHAR, -1, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  878             goto error;
  879         output_var.Assign(dwResult + 1);
  880         break;
  881 
  882     case CONTROLGET_CMD_CURRENTCOL:
  883     {
  884         DWORD_PTR line_number;
  885         // The dwResult from the first msg below is not useful and is not checked.
  886         if (   !SendMessageTimeout(control_window, EM_GETSEL, (WPARAM)&start, (LPARAM)&end, SMTO_ABORTIFHUNG, 2000, &dwResult)
  887             || !SendMessageTimeout(control_window, EM_LINEFROMCHAR, (WPARAM)start, 0, SMTO_ABORTIFHUNG, 2000, &line_number)   )
  888             goto error;
  889         if (!line_number) // Since we're on line zero, the column number is simply start+1.
  890         {
  891             output_var.Assign(start + 1);  // +1 to convert from zero based.
  892             break; // Fall out of the switch so that ErrorLevel will be set to 0 (no error).
  893         }
  894         // Au3: Decrement the character index until the row changes.  Difference between this
  895         // char index and original is the column:
  896         DWORD_PTR start_orig = start;  // Au3: the character index
  897         for (;;)
  898         {
  899             if (!SendMessageTimeout(control_window, EM_LINEFROMCHAR, (WPARAM)start, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  900                 goto error;
  901             if (dwResult != line_number)
  902                 break;
  903             --start;
  904         }
  905         output_var.Assign((int)(start_orig - start));
  906         break;
  907     }
  908 
  909     case CONTROLGET_CMD_LINE:
  910         if (!*aValue)
  911             goto error;
  912         control_index = ATOI(aValue) - 1;
  913         if (control_index < 0)
  914             goto error;
  915         // jackieku: 32768 * sizeof(wchar_t) = 65536, which can not be stored in a unsigned 16bit integer.
  916         dyn_buf = (LPTSTR)talloca(32767); // 32768 is the size Au3 uses for GETLINE and such.
  917         *(LPWORD)dyn_buf = 32767; // EM_GETLINE requires first word of string to be set to its size.
  918         if (!SendMessageTimeout(control_window, EM_GETLINE, (WPARAM)control_index, (LPARAM)dyn_buf, SMTO_ABORTIFHUNG, 2000, &dwResult))
  919             goto error;
  920         if (!dwResult) // Line is empty or line number is invalid.
  921         {
  922             DWORD_PTR dwLineCount;
  923             if (!SendMessageTimeout(control_window, EM_GETLINECOUNT, 0, 0, SMTO_ABORTIFHUNG, 2000, &dwLineCount)
  924                 || (DWORD)control_index > dwLineCount)
  925                 goto error;
  926         }
  927         dyn_buf[dwResult] = '\0'; // Ensure terminated since the API might not do it in some cases.
  928         output_var.Assign(dyn_buf);
  929         break;
  930 
  931     case CONTROLGET_CMD_SELECTED: // Must be an Edit.
  932         // Note: The RichEdit controls of certain apps such as Metapad don't return the right selection
  933         // with this technique.  Au3 has the same problem with them, so for now it's just documented here
  934         // as a limitation.
  935         if (!SendMessageTimeout(control_window, EM_GETSEL, (WPARAM)&start, (LPARAM)&end, SMTO_ABORTIFHUNG, 2000, &dwResult))
  936             goto error;
  937         // The above sets start to be the zero-based position of the start of the selection (similar for end).
  938         // If there is no selection, start and end will be equal, at least in the edit controls I tried it with.
  939         // The dwResult from the above is not useful and is not checked.
  940         if (start == end) // Unlike Au3, it seems best to consider a blank selection to be a non-error.
  941         {
  942             output_var.Assign();
  943             break; // Fall out of the switch so that ErrorLevel will be set to 0 (no error).
  944         }
  945         // Dynamic memory is used because must get all the control's text so that just the selected region
  946         // can be cropped out and assigned to the output variable.  Otherwise, output_var might
  947         // have to be sized much larger than it would need to be:
  948         if (   !SendMessageTimeout(control_window, WM_GETTEXTLENGTH, 0, 0, SMTO_ABORTIFHUNG, 2000, &length)
  949             || !length  // Since the above didn't return for start == end, this is an error because we have a selection of non-zero length, but no text to go with it!
  950             || !(dyn_buf = tmalloc(length + 1))   ) // Relies on short-circuit boolean order.
  951             goto error;
  952         if (   !SendMessageTimeout(control_window, WM_GETTEXT, (WPARAM)(length + 1), (LPARAM)dyn_buf, SMTO_ABORTIFHUNG, 2000, &length)
  953             || !length || end > length   )
  954         {
  955             // The first check above is reveals a problem (ErrorLevel = 1) since the length
  956             // is unexpectedly zero (above implied it shouldn't be).  The second check is also
  957             // a problem because the end of the selection should not be beyond length of text
  958             // that was retrieved.
  959             free(dyn_buf);
  960             goto error;
  961         }
  962         dyn_buf[end] = '\0'; // Terminate the string at the end of the selection.
  963         output_var.Assign(dyn_buf + start);
  964         free(dyn_buf);
  965         break;
  966 
  967     case CONTROLGET_CMD_STYLE:
  968         // Seems best to always format as hex, since it has more human-readable meaning then:
  969         _stprintf(temp_buf, _T("0x%08X"), GetWindowLong(control_window, GWL_STYLE));
  970         output_var.Assign(temp_buf);
  971         break;
  972 
  973     case CONTROLGET_CMD_EXSTYLE:
  974         // Seems best to always format as hex, since it has more human-readable meaning then:
  975         _stprintf(temp_buf, _T("0x%08X"), GetWindowLong(control_window, GWL_EXSTYLE));
  976         output_var.Assign(temp_buf);
  977         break;
  978 
  979     case CONTROLGET_CMD_HWND:
  980         // The terminology "HWND" was chosen rather than "ID" to avoid confusion with a control's
  981         // dialog ID (as retrieved by GetDlgCtrlID).  This also reserves the word ID for possible
  982         // use with the control's Dialog ID in future versions.
  983         output_var.AssignHWND(control_window);
  984         break;
  985     }
  986 
  987     // Note that ControlDelay is not done for the Get type commands, because it seems unnecessary.
  988     return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  989 
  990 error:
  991     output_var.Assign();
  992     return SetErrorLevelOrThrow();
  993 }
  994 
  995 
  996 
  997 ResultType Line::URLDownloadToFile(LPTSTR aURL, LPTSTR aFilespec)
  998 {
  999     // v1.0.44.07: Set default to INTERNET_FLAG_RELOAD vs. 0 because the vast majority of usages would want
 1000     // the file to be retrieved directly rather than from the cache.
 1001     // v1.0.46.04: Added more no-cache flags because otherwise, it definitely falls back to the cache if
 1002     // the remote server doesn't respond (and perhaps other errors), which defeats the ability to use
 1003     // UrlDownloadToFile for uptime/server monitoring.  Also, in spite of what MSDN says, it seems nearly
 1004     // certain based on other sources that more than one flag is supported.  Someone also mentioned that
 1005     // INTERNET_FLAG_CACHE_IF_NET_FAIL is related to this, but there's no way to specify it in these
 1006     // particular calls, and it's the opposite of the desired behavior anyway; so it seems impossible to
 1007     // turn it off explicitly.
 1008     DWORD flags_for_open_url = INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE;
 1009     aURL = omit_leading_whitespace(aURL);
 1010     if (*aURL == '*') // v1.0.44.07: Provide an option to override flags_for_open_url.
 1011     {
 1012         flags_for_open_url = ATOU(++aURL);
 1013         LPTSTR cp;
 1014         if (cp = StrChrAny(aURL, _T(" \t"))) // Find first space or tab.
 1015             aURL = omit_leading_whitespace(cp);
 1016     }
 1017 
 1018     // Open the internet session. v1.0.45.03: Provide a non-NULL user-agent because  some servers reject
 1019     // requests that lack a user-agent.  Furthermore, it's more professional to have one, in which case it
 1020     // should probably be kept as simple and unchanging as possible.  Using something like the script's name
 1021     // as the user agent (even if documented) seems like a bad idea because it might contain personal/sensitive info.
 1022     HINTERNET hInet = InternetOpen(_T("AutoHotkey"), INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY, NULL, NULL, 0);
 1023     if (!hInet)
 1024         return SetErrorLevelOrThrow();
 1025 
 1026     // Open the required URL
 1027     HINTERNET hFile = InternetOpenUrl(hInet, aURL, NULL, 0, flags_for_open_url, 0);
 1028     if (!hFile)
 1029     {
 1030         InternetCloseHandle(hInet);
 1031         return SetErrorLevelOrThrow();
 1032     }
 1033 
 1034     // Open our output file
 1035     FILE *fptr = _tfopen(aFilespec, _T("wb"));  // Open in binary write/destroy mode
 1036     if (!fptr)
 1037     {
 1038         InternetCloseHandle(hFile);
 1039         InternetCloseHandle(hInet);
 1040         return SetErrorLevelOrThrow();
 1041     }
 1042 
 1043     BYTE bufData[1024 * 1]; // v1.0.44.11: Reduced from 8 KB to alleviate GUI window lag during UrlDownloadtoFile.  Testing shows this reduction doesn't affect performance on high-speed downloads (in fact, downloads are slightly faster; I tested two sites, one at 184 KB/s and the other at 380 KB/s).  It might affect slow downloads, but that seems less likely so wasn't tested.
 1044     INTERNET_BUFFERSA buffers = {0};
 1045     buffers.dwStructSize = sizeof(INTERNET_BUFFERSA);
 1046     buffers.lpvBuffer = bufData;
 1047     buffers.dwBufferLength = sizeof(bufData);
 1048 
 1049     LONG_OPERATION_INIT
 1050 
 1051     // Read the file.  I don't think synchronous transfers typically generate the pseudo-error
 1052     // ERROR_IO_PENDING, so that is not checked here.  That's probably just for async transfers.
 1053     // IRF_NO_WAIT is used to avoid requiring the call to block until the buffer is full.  By
 1054     // having it return the moment there is any data in the buffer, the program is made more
 1055     // responsive, especially when the download is very slow and/or one of the hooks is installed:
 1056     BOOL result;
 1057     if (*aURL == 'h' || *aURL == 'H')
 1058     {
 1059         while (result = InternetReadFileExA(hFile, &buffers, IRF_NO_WAIT, NULL)) // Assign
 1060         {
 1061             if (!buffers.dwBufferLength) // Transfer is complete.
 1062                 break;
 1063             LONG_OPERATION_UPDATE  // Done in between the net-read and the file-write to improve avg. responsiveness.
 1064             fwrite(bufData, buffers.dwBufferLength, 1, fptr);
 1065             buffers.dwBufferLength = sizeof(bufData);  // Reset buffer capacity for next iteration.
 1066         }
 1067     }
 1068     else // v1.0.48.04: This section adds support for FTP and perhaps Gopher by using InternetReadFile() instead of InternetReadFileEx().
 1069     {
 1070         DWORD number_of_bytes_read;
 1071         while (result = InternetReadFile(hFile, bufData, sizeof(bufData), &number_of_bytes_read))
 1072         {
 1073             if (!number_of_bytes_read)
 1074                 break;
 1075             LONG_OPERATION_UPDATE
 1076             fwrite(bufData, number_of_bytes_read, 1, fptr);
 1077         }
 1078     }
 1079     // Close internet session:
 1080     InternetCloseHandle(hFile);
 1081     InternetCloseHandle(hInet);
 1082     // Close output file:
 1083     fclose(fptr);
 1084 
 1085     if (!result) // An error occurred during the transfer.
 1086         DeleteFile(aFilespec);  // Delete damaged/incomplete file.
 1087     return SetErrorLevelOrThrowBool(!result);
 1088 }
 1089 
 1090 
 1091 
 1092 int CALLBACK FileSelectFolderCallback(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
 1093 {
 1094     if (uMsg == BFFM_INITIALIZED) // Caller has ensured that lpData isn't NULL by having set a valid lParam value.
 1095         SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
 1096     // In spite of the quote below, the behavior does not seem to vary regardless of what value is returned
 1097     // upon receipt of BFFM_VALIDATEFAILED, at least on XP.  But in case it matters on other OSes, preserve
 1098     // compatibility with versions older than 1.0.36.03 by keeping the dialog displayed even if the user enters
 1099     // an invalid folder:
 1100     // MSDN: "Returns zero except in the case of BFFM_VALIDATEFAILED. For that flag, returns zero to dismiss
 1101     // the dialog or nonzero to keep the dialog displayed."
 1102     return uMsg == BFFM_VALIDATEFAILED; // i.e. zero should be returned in almost every case.
 1103 }
 1104 
 1105 
 1106 
 1107 ResultType Line::FileSelectFolder(LPTSTR aRootDir, LPTSTR aOptions, LPTSTR aGreeting)
 1108 // Since other script threads can interrupt this command while it's running, it's important that
 1109 // the command not refer to sArgDeref[] and sArgVar[] anytime after an interruption becomes possible.
 1110 // This is because an interrupting thread usually changes the values to something inappropriate for this thread.
 1111 {
 1112     Var &output_var = *OUTPUT_VAR; // Must be resolved early.  See comment above.
 1113     if (!output_var.Assign())  // Initialize the output variable.
 1114         return FAIL;
 1115 
 1116     if (g_nFolderDialogs >= MAX_FOLDERDIALOGS)
 1117     {
 1118         // Have a maximum to help prevent runaway hotkeys due to key-repeat feature, etc.
 1119         return LineError(_T("The maximum number of Folder Dialogs has been reached."));
 1120     }
 1121 
 1122     LPMALLOC pMalloc;
 1123     if (SHGetMalloc(&pMalloc) != NOERROR)   // Initialize
 1124         return SetErrorLevelOrThrow();
 1125 
 1126     // v1.0.36.03: Support initial folder, which is different than the root folder because the root only
 1127     // controls the origin point (above which the control cannot navigate).
 1128     LPTSTR initial_folder;
 1129     TCHAR root_dir[MAX_PATH*2 + 5];  // Up to two paths might be present inside, including an asterisk and spaces between them.
 1130     tcslcpy(root_dir, aRootDir, _countof(root_dir)); // Make a modifiable copy.
 1131     if (initial_folder = _tcschr(root_dir, '*'))
 1132     {
 1133         *initial_folder = '\0'; // Terminate so that root_dir becomes an isolated string.
 1134         // Must eliminate the trailing whitespace or it won't work.  However, only up to one space or tab
 1135         // so that path names that really do end in literal spaces can be used:
 1136         if (initial_folder > root_dir && IS_SPACE_OR_TAB(initial_folder[-1]))
 1137             initial_folder[-1] = '\0';
 1138         // In case absolute paths can ever have literal leading whitespace, preserve that whitespace
 1139         // by incrementing by only one and not calling omit_leading_whitespace().  This has been documented.
 1140         ++initial_folder;
 1141     }
 1142     else
 1143         initial_folder = NULL;
 1144     if (!*(omit_leading_whitespace(root_dir))) // Count all-whitespace as a blank string, but retain leading whitespace if there is also non-whitespace inside.
 1145         *root_dir = '\0';
 1146 
 1147     BROWSEINFO bi;
 1148     if (initial_folder)
 1149     {
 1150         bi.lpfn = FileSelectFolderCallback;
 1151         bi.lParam = (LPARAM)initial_folder;  // Used by the callback above.
 1152     }
 1153     else
 1154         bi.lpfn = NULL;  // It will ignore the value of bi.lParam when lpfn is NULL.
 1155 
 1156     if (*root_dir)
 1157     {
 1158         IShellFolder *pDF;
 1159         if (SHGetDesktopFolder(&pDF) == NOERROR)
 1160         {
 1161             LPITEMIDLIST pIdl = NULL;
 1162             ULONG        chEaten;
 1163             ULONG        dwAttributes;
 1164 #ifdef UNICODE
 1165             pDF->ParseDisplayName(NULL, NULL, root_dir, &chEaten, &pIdl, &dwAttributes);
 1166 #else
 1167             OLECHAR olePath[MAX_PATH];          // wide-char version of path name
 1168             ToWideChar(root_dir, olePath, MAX_PATH); // Dest. size is in wchars, not bytes.
 1169             pDF->ParseDisplayName(NULL, NULL, olePath, &chEaten, &pIdl, &dwAttributes);
 1170 #endif
 1171             pDF->Release();
 1172             bi.pidlRoot = pIdl;
 1173         }
 1174     }
 1175     else // No root directory.
 1176         bi.pidlRoot = NULL;  // Make it use "My Computer" as the root dir.
 1177 
 1178     int iImage = 0;
 1179     bi.iImage = iImage;
 1180     bi.hwndOwner = THREAD_DIALOG_OWNER; // Can be NULL, which is used rather than main window since no need to have main window forced into the background by this.
 1181     TCHAR greeting[1024];
 1182     if (aGreeting && *aGreeting)
 1183         tcslcpy(greeting, aGreeting, _countof(greeting));
 1184     else
 1185         sntprintf(greeting, _countof(greeting), _T("Select Folder - %s"), g_script.mFileName);
 1186     bi.lpszTitle = greeting;
 1187 
 1188     DWORD options = *aOptions ? ATOI(aOptions) : FSF_ALLOW_CREATE;
 1189     bi.ulFlags =
 1190           ((options & FSF_NONEWDIALOG)    ? 0           : BIF_NEWDIALOGSTYLE) // v1.0.48: Added to support BartPE/WinPE.
 1191         | ((options & FSF_ALLOW_CREATE)   ? 0           : BIF_NONEWFOLDERBUTTON)
 1192         | ((options & FSF_EDITBOX)        ? BIF_EDITBOX : 0);
 1193 
 1194     TCHAR Result[2048];
 1195     bi.pszDisplayName = Result;  // This will hold the user's choice.
 1196 
 1197     // At this point, we know a dialog will be displayed.  See macro's comments for details:
 1198     DIALOG_PREP
 1199     POST_AHK_DIALOG(0) // Do this only after the above.  Must pass 0 for timeout in this case.
 1200 
 1201     ++g_nFolderDialogs;
 1202     LPITEMIDLIST lpItemIDList = SHBrowseForFolder(&bi);  // Spawn Dialog
 1203     --g_nFolderDialogs;
 1204 
 1205     DIALOG_END
 1206     if (!lpItemIDList)
 1207         // Due to rarity and because there doesn't seem to be any way to detect it,
 1208         // no exception is thrown when the function fails.  Instead, we just assume
 1209         // that the user pressed CANCEL (which should not be treated as an error):
 1210         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
 1211 
 1212     *Result = '\0';  // Reuse this var, this time to old the result of the below:
 1213     SHGetPathFromIDList(lpItemIDList, Result);
 1214     pMalloc->Free(lpItemIDList);
 1215     pMalloc->Release();
 1216 
 1217     g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
 1218     return output_var.Assign(Result);
 1219 }
 1220 
 1221 
 1222 
 1223 ResultType Line::FileGetShortcut(LPTSTR aShortcutFile) // Credited to Holger <Holger.Kotsch at GMX de>.
 1224 {
 1225     Var *output_var_target = ARGVAR2; // These might be omitted in the parameter list, so it's okay if 
 1226     Var *output_var_dir = ARGVAR3;    // they resolve to NULL.  Also, load-time validation has ensured
 1227     Var *output_var_arg = ARGVAR4;    // that these are valid output variables (e.g. not built-in vars).
 1228     Var *output_var_desc = ARGVAR5;   // Load-time validation has ensured that these are valid output variables (e.g. not built-in vars).
 1229     Var *output_var_icon = ARGVAR6;
 1230     Var *output_var_icon_idx = ARGVAR7;
 1231     Var *output_var_show_state = ARGVAR8;
 1232 
 1233     // For consistency with the behavior of other commands, the output variables are initialized to blank
 1234     // so that there is another way to detect failure:
 1235     if (output_var_target) output_var_target->Assign();
 1236     if (output_var_dir) output_var_dir->Assign();
 1237     if (output_var_arg) output_var_arg->Assign();
 1238     if (output_var_desc) output_var_desc->Assign();
 1239     if (output_var_icon) output_var_icon->Assign();
 1240     if (output_var_icon_idx) output_var_icon_idx->Assign();
 1241     if (output_var_show_state) output_var_show_state->Assign();
 1242 
 1243     bool bSucceeded = false;
 1244 
 1245     if (!Util_DoesFileExist(aShortcutFile))
 1246         goto error;
 1247 
 1248     CoInitialize(NULL);
 1249     IShellLink *psl;
 1250 
 1251     if (SUCCEEDED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl)))
 1252     {
 1253         IPersistFile *ppf;
 1254         if (SUCCEEDED(psl->QueryInterface(IID_IPersistFile, (LPVOID *)&ppf)))
 1255         {
 1256 #ifdef UNICODE
 1257             if (SUCCEEDED(ppf->Load(aShortcutFile, 0)))
 1258 #else
 1259             WCHAR wsz[MAX_PATH+1]; // +1 hasn't been explained, but is retained in case it's needed.
 1260             ToWideChar(aShortcutFile, wsz, MAX_PATH+1); // Dest. size is in wchars, not bytes.
 1261             if (SUCCEEDED(ppf->Load((const WCHAR*)wsz, 0)))
 1262 #endif
 1263             {
 1264                 TCHAR buf[MAX_PATH+1];
 1265                 int icon_index, show_cmd;
 1266 
 1267                 if (output_var_target)
 1268                 {
 1269                     psl->GetPath(buf, MAX_PATH, NULL, SLGP_UNCPRIORITY);
 1270                     output_var_target->Assign(buf);
 1271                 }
 1272                 if (output_var_dir)
 1273                 {
 1274                     psl->GetWorkingDirectory(buf, MAX_PATH);
 1275                     output_var_dir->Assign(buf);
 1276                 }
 1277                 if (output_var_arg)
 1278                 {
 1279                     psl->GetArguments(buf, MAX_PATH);
 1280                     output_var_arg->Assign(buf);
 1281                 }
 1282                 if (output_var_desc)
 1283                 {
 1284                     psl->GetDescription(buf, MAX_PATH); // Testing shows that the OS limits it to 260 characters.
 1285                     output_var_desc->Assign(buf);
 1286                 }
 1287                 if (output_var_icon || output_var_icon_idx)
 1288                 {
 1289                     psl->GetIconLocation(buf, MAX_PATH, &icon_index);
 1290                     if (output_var_icon)
 1291                         output_var_icon->Assign(buf);
 1292                     if (output_var_icon_idx)
 1293                         if (*buf)
 1294                             output_var_icon_idx->Assign(icon_index + (icon_index >= 0 ? 1 : 0));  // Convert from 0-based to 1-based for consistency with the Menu command, etc. but leave negative resource IDs as-is.
 1295                         else
 1296                             output_var_icon_idx->Assign(); // Make it blank to indicate that there is none.
 1297                 }
 1298                 if (output_var_show_state)
 1299                 {
 1300                     psl->GetShowCmd(&show_cmd);
 1301                     output_var_show_state->Assign(show_cmd);
 1302                     // For the above, decided not to translate them to Max/Min/Normal since other
 1303                     // show-state numbers might be supported in the future (or are already).  In other
 1304                     // words, this allows the flexibility to specify some number other than 1/3/7 when
 1305                     // creating the shortcut in case it happens to work.  Of course, that applies only
 1306                     // to FileCreateShortcut, not here.  But it's done here so that this command is
 1307                     // compatible with that one.
 1308                 }
 1309                 g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
 1310                 bSucceeded = true;
 1311             }
 1312             ppf->Release();
 1313         }
 1314         psl->Release();
 1315     }
 1316     CoUninitialize();
 1317 
 1318     if (!bSucceeded)
 1319         goto error;
 1320 
 1321     return OK;  // ErrorLevel might still indicate failure if one of the above calls failed.
 1322 
 1323 error:
 1324     return SetErrorLevelOrThrow();
 1325 }
 1326 
 1327 
 1328 
 1329 ResultType Line::FileCreateShortcut(LPTSTR aTargetFile, LPTSTR aShortcutFile, LPTSTR aWorkingDir, LPTSTR aArgs
 1330     , LPTSTR aDescription, LPTSTR aIconFile, LPTSTR aHotkey, LPTSTR aIconNumber, LPTSTR aRunState)
 1331 {
 1332     bool bSucceeded = false;
 1333     CoInitialize(NULL);
 1334     IShellLink *psl;
 1335 
 1336     if (SUCCEEDED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl)))
 1337     {
 1338         psl->SetPath(aTargetFile);
 1339         if (*aWorkingDir)
 1340             psl->SetWorkingDirectory(aWorkingDir);
 1341         if (*aArgs)
 1342             psl->SetArguments(aArgs);
 1343         if (*aDescription)
 1344             psl->SetDescription(aDescription);
 1345         int icon_index = *aIconNumber ? ATOI(aIconNumber) : 0;
 1346         if (*aIconFile)
 1347             psl->SetIconLocation(aIconFile,  icon_index - (icon_index > 0 ? 1 : 0)); // Convert 1-based index to 0-based, but leave negative resource IDs as-is.
 1348         if (*aHotkey)
 1349         {
 1350             // If badly formatted, it's not a critical error, just continue.
 1351             // Currently, only shortcuts with a CTRL+ALT are supported.
 1352             // AutoIt3 note: Make sure that CTRL+ALT is selected (otherwise invalid)
 1353             vk_type vk = TextToVK(aHotkey);
 1354             if (vk)
 1355                 // Vk in low 8 bits, mods in high 8:
 1356                 psl->SetHotkey(   (WORD)vk | ((WORD)(HOTKEYF_CONTROL | HOTKEYF_ALT) << 8)   );
 1357         }
 1358         if (*aRunState)
 1359             psl->SetShowCmd(ATOI(aRunState)); // No validation is done since there's a chance other numbers might be valid now or in the future.
 1360 
 1361         IPersistFile *ppf;
 1362         if (SUCCEEDED(psl->QueryInterface(IID_IPersistFile,(LPVOID *)&ppf)))
 1363         {
 1364 #ifndef UNICODE
 1365             WCHAR wsz[MAX_PATH];
 1366             ToWideChar(aShortcutFile, wsz, MAX_PATH); // Dest. size is in wchars, not bytes.
 1367 #else
 1368             LPCWSTR wsz = aShortcutFile;
 1369 #endif
 1370             // MSDN says to pass "The absolute path of the file".  Windows 10 requires it.
 1371             WCHAR full_path[MAX_PATH];
 1372             GetFullPathNameW(wsz, _countof(full_path), full_path, NULL);
 1373             if (SUCCEEDED(ppf->Save(full_path, TRUE)))
 1374             {
 1375                 g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
 1376                 bSucceeded = true;
 1377             }
 1378             ppf->Release();
 1379         }
 1380         psl->Release();
 1381     }
 1382 
 1383     CoUninitialize();
 1384     if (bSucceeded)
 1385         return OK;
 1386     else
 1387         return SetErrorLevelOrThrow();
 1388 }
 1389 
 1390 
 1391 
 1392 ResultType Line::FileRecycle(LPTSTR aFilePattern)
 1393 {
 1394     if (!aFilePattern || !*aFilePattern)
 1395         return SetErrorLevelOrThrow();  // Since this is probably not what the user intended.
 1396 
 1397     SHFILEOPSTRUCT FileOp;
 1398     TCHAR szFileTemp[_MAX_PATH+2];
 1399 
 1400     // au3: Get the fullpathname - required for UNDO to work
 1401     Util_GetFullPathName(aFilePattern, szFileTemp);
 1402 
 1403     // au3: We must also make it a double nulled string *sigh*
 1404     szFileTemp[_tcslen(szFileTemp)+1] = '\0';
 1405 
 1406     // au3: set to known values - Corrects crash
 1407     FileOp.hNameMappings = NULL;
 1408     FileOp.lpszProgressTitle = NULL;
 1409     FileOp.fAnyOperationsAborted = FALSE;
 1410     FileOp.hwnd = NULL;
 1411     FileOp.pTo = NULL;
 1412 
 1413     FileOp.pFrom = szFileTemp;
 1414     FileOp.wFunc = FO_DELETE;
 1415     FileOp.fFlags = FOF_SILENT | FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_WANTNUKEWARNING;
 1416 
 1417     // SHFileOperation() returns 0 on success:
 1418     return SetErrorLevelOrThrowBool(SHFileOperation(&FileOp));
 1419 }
 1420 
 1421 
 1422 
 1423 ResultType Line::FileRecycleEmpty(LPTSTR aDriveLetter)
 1424 {
 1425     LPCTSTR szPath = *aDriveLetter ? aDriveLetter : NULL;
 1426     HRESULT hr = SHEmptyRecycleBin(NULL, szPath, SHERB_NOCONFIRMATION | SHERB_NOPROGRESSUI | SHERB_NOSOUND);
 1427     return SetErrorLevelOrThrowBool(hr != S_OK);
 1428 }
 1429 
 1430 
 1431 
 1432 ResultType Line::FileGetVersion(LPTSTR aFilespec)
 1433 {
 1434     OUTPUT_VAR->Assign(); // Init to be blank, in case of failure.
 1435 
 1436     if (!aFilespec || !*aFilespec)
 1437         return SetErrorsOrThrow(true, ERROR_INVALID_PARAMETER); // Error out, since this is probably not what the user intended.
 1438 
 1439     DWORD dwUnused, dwSize;
 1440     if (   !(dwSize = GetFileVersionInfoSize(aFilespec, &dwUnused))   )  // No documented limit on how large it can be, so don't use _alloca().
 1441         return SetErrorsOrThrow(true);
 1442 
 1443     BYTE *pInfo = (BYTE*)malloc(dwSize);  // Allocate the size retrieved by the above.
 1444     VS_FIXEDFILEINFO *pFFI;
 1445     UINT uSize;
 1446 
 1447     // Read the version resource
 1448     if (!GetFileVersionInfo(aFilespec, 0, dwSize, (LPVOID)pInfo)
 1449     // Locate the fixed information
 1450         || !VerQueryValue(pInfo, _T("\\"), (LPVOID *)&pFFI, &uSize))
 1451     {
 1452         g->LastError = GetLastError();
 1453         free(pInfo);
 1454         return SetErrorLevelOrThrow();
 1455     }
 1456 
 1457     // extract the fields you want from pFFI
 1458     UINT iFileMS = (UINT)pFFI->dwFileVersionMS;
 1459     UINT iFileLS = (UINT)pFFI->dwFileVersionLS;
 1460     TCHAR version_string[128];  // AutoIt3: 43+1 is the maximum size, but leave a little room to increase confidence.
 1461     sntprintf(version_string, _countof(version_string), _T("%u.%u.%u.%u")
 1462         , (iFileMS >> 16), (iFileMS & 0xFFFF), (iFileLS >> 16), (iFileLS & 0xFFFF));
 1463 
 1464     free(pInfo);
 1465 
 1466     SetErrorsOrThrow(false, 0); // Indicate success.
 1467     return OUTPUT_VAR->Assign(version_string);
 1468 }
 1469 
 1470 
 1471 
 1472 bool Line::Util_CopyDir(LPCTSTR szInputSource, LPCTSTR szInputDest, int OverwriteMode, bool bMove)
 1473 {
 1474     bool bOverwrite = OverwriteMode == 1 || OverwriteMode == 2; // Strict validation for safety.
 1475 
 1476     // Get the fullpathnames and strip trailing \s
 1477     TCHAR szSource[_MAX_PATH+2];
 1478     TCHAR szDest[_MAX_PATH+2];
 1479     Util_GetFullPathName(szInputSource, szSource);
 1480     Util_GetFullPathName(szInputDest, szDest);
 1481 
 1482     // Ensure source is a directory
 1483     if (Util_IsDir(szSource) == false)
 1484         return false;                           // Nope
 1485 
 1486     // Jon on the AutoIt forums says "Well SHFileOp is way too unpredictable under 9x. Grrr."
 1487     // So the comments below about some OSes/old versions are probably just referring to 9x,
 1488     // which we don't support anymore.  Testing on Windows 2000 and Windows 10 showed that
 1489     // SHFileOperation can move a directory between two volumes.  However, performing copy
 1490     // then remove ensures nothing is removed if the copy partially fails, so it's kept this
 1491     // way for backward-compatibility, even though it may be inconsistent with local moves.
 1492     // Obsolete comment from Util_MoveDir:
 1493     // If the source and dest are on different volumes then we must copy rather than move
 1494     // as move in this case only works on some OSes.  Copy and delete (poor man's move).
 1495     if (bMove && (ctolower(szSource[0]) != ctolower(szDest[0]) || szSource[1] != ':'))
 1496     {
 1497         if (!Util_CopyDir(szSource, szDest, bOverwrite, false))
 1498             return false;
 1499         return Util_RemoveDir(szSource, true);
 1500     }
 1501 
 1502     // Does the destination dir exist?
 1503     DWORD attr = GetFileAttributes(szDest);
 1504     if (attr != 0xFFFFFFFF) // Destination already exists as a file or directory.
 1505     {
 1506         if (attr & FILE_ATTRIBUTE_DIRECTORY) // Dest already exists as a directory.
 1507         {
 1508             if (!bOverwrite) // Overwrite Mode is "Never".
 1509                 return false;
 1510         }
 1511         else // Dest already exists as a file.
 1512             return false; // Don't even attempt to overwrite a file with a dir, regardless of mode (I think SHFileOperation refuses to do it anyway).
 1513     }
 1514     else // Dest doesn't exist.
 1515     {
 1516         // We must create the top level directory
 1517         // FOF_SILENT (which is included in FOF_NO_UI and means "Do not display a progress dialog box")
 1518         // seems to be bugged on some older OSes (such as 2k and XP).  Specifically, it answers "No" to
 1519         // the "confirmmkdir" dialog, which it isn't supposed to suppress, and ignores FOF_NOCONFIRMMKDIR.
 1520         // Creating the directory first works around this.  Win 7 is okay without this; Vista wasn't tested.
 1521         if (!bMove && !FileCreateDir(szDest))
 1522             return false;
 1523     }
 1524 
 1525     // The wildcard below is kept for backward-compatibility, although as indicated above, the
 1526     // issues alluded to below are probably only on 9x, which is no longer supported.  Adding the
 1527     // wildcard appears to permit copying a directory into itself (perhaps because the directory
 1528     // itself isn't being copied), although we still document the result as "undefined".
 1529     // Really old comment:
 1530     // To work under old versions AND new version of shell32.dll the source must be specified
 1531     // as "dir\*.*" and the destination directory must already exist... Goddamn Microsoft and their APIs...
 1532     if (!bMove)
 1533         _tcscat(szSource, _T("\\*.*"));
 1534 
 1535     // We must also make source\dest double nulled strings for the SHFileOp API
 1536     szSource[_tcslen(szSource)+1] = '\0';   
 1537     szDest[_tcslen(szDest)+1] = '\0';   
 1538 
 1539     // Setup the struct
 1540     SHFILEOPSTRUCT FileOp = {0};
 1541     FileOp.pFrom = szSource;
 1542     FileOp.pTo = szDest;
 1543     FileOp.wFunc = bMove ? FO_MOVE : FO_COPY;
 1544     FileOp.fFlags = FOF_NO_UI; // Set default.
 1545     if (OverwriteMode == 2)
 1546         FileOp.fFlags |= FOF_MULTIDESTFILES; // v1.0.46.07: Using the FOF_MULTIDESTFILES flag (as hinted by MSDN) overwrites/merges any existing target directory.  This logic supersedes and fixes old logic that didn't work properly when the source dir was being both renamed and moved to overwrite an existing directory.
 1547     // All of the below left set to NULL/FALSE by the struct initializer higher above:
 1548     //FileOp.hNameMappings          = NULL;
 1549     //FileOp.lpszProgressTitle      = NULL;
 1550     //FileOp.fAnyOperationsAborted  = FALSE;
 1551     //FileOp.hwnd                   = NULL;
 1552 
 1553     // If the source directory contains any saved webpages consisting of a SiteName.htm file and a
 1554     // corresponding directory named SiteName_files, the following may indicate an error even when the
 1555     // copy is successful. Under Windows XP at least, the return value is 7 under these conditions,
 1556     // which according to WinError.h is "ERROR_ARENA_TRASHED: The storage control blocks were destroyed."
 1557     // However, since this error might occur under a variety of circumstances, it probably wouldn't be
 1558     // proper to consider it a non-error.
 1559     // I also checked GetLastError() after calling SHFileOperation(), but it does not appear to be
 1560     // valid/useful in this case (MSDN mentions this fact but isn't clear about it).
 1561     // The issue appears to affect only FileCopyDir, not FileMoveDir or FileRemoveDir.  It also seems
 1562     // unlikely to affect FileCopy/FileMove because they never copy directories.
 1563     return !SHFileOperation(&FileOp);
 1564 }
 1565 
 1566 
 1567 
 1568 bool Line::Util_RemoveDir(LPCTSTR szInputSource, bool bRecurse)
 1569 {
 1570     SHFILEOPSTRUCT  FileOp;
 1571     TCHAR           szSource[_MAX_PATH+2];
 1572     
 1573     // If recursion not on just try a standard delete on the directory (the SHFile function WILL
 1574     // delete a directory even if not empty no matter what flags you give it...)
 1575     if (bRecurse == false)
 1576     {
 1577         // v1.1.31.00: Use the original source path in case its length exceeds _MAX_PATH.
 1578         // Relative paths and trailing slashes are okay in this case, and Util_IsDir() is
 1579         // not needed since the function only removes empty directories, not files.
 1580         if (!RemoveDirectory(szInputSource))
 1581             return false;
 1582         else
 1583             return true;
 1584     }
 1585 
 1586     // Get the fullpathnames and strip trailing \s
 1587     Util_GetFullPathName(szInputSource, szSource);
 1588 
 1589     // Ensure source is a directory
 1590     if (Util_IsDir(szSource) == false)
 1591         return false;                           // Nope
 1592 
 1593     // We must also make double nulled strings for the SHFileOp API
 1594     szSource[_tcslen(szSource)+1] = '\0';
 1595 
 1596     // Setup the struct
 1597     FileOp.pFrom                    = szSource;
 1598     FileOp.pTo                      = NULL;
 1599     FileOp.hNameMappings            = NULL;
 1600     FileOp.lpszProgressTitle        = NULL;
 1601     FileOp.fAnyOperationsAborted    = FALSE;
 1602     FileOp.hwnd                     = NULL;
 1603 
 1604     FileOp.wFunc    = FO_DELETE;
 1605     FileOp.fFlags   = FOF_SILENT | FOF_NOCONFIRMMKDIR | FOF_NOCONFIRMATION | FOF_NOERRORUI;
 1606     
 1607     return !SHFileOperation(&FileOp);
 1608 }
 1609 
 1610 
 1611 
 1612 ///////////////////////////////////////////////////////////////////////////////
 1613 // Util_CopyFile()
 1614 // (moves files too)
 1615 // Returns the number of files that could not be copied or moved due to error.
 1616 ///////////////////////////////////////////////////////////////////////////////
 1617 int Line::Util_CopyFile(LPCTSTR szInputSource, LPCTSTR szInputDest, bool bOverwrite, bool bMove, DWORD &aLastError)
 1618 {
 1619     TCHAR szSource[T_MAX_PATH];
 1620     TCHAR szDest[T_MAX_PATH];
 1621     TCHAR szDestPattern[MAX_PATH];
 1622 
 1623     // Get local version of our source/dest with full path names, strip trailing \s
 1624     // and normalize the path separator (replace / with \).
 1625     Util_GetFullPathName(szInputSource, szSource, _countof(szSource));
 1626     Util_GetFullPathName(szInputDest, szDest, _countof(szDest));
 1627 
 1628     // If the source or dest is a directory then add *.* to the end
 1629     if (Util_IsDir(szSource))
 1630         _tcscat(szSource, _T("\\*.*"));
 1631     if (Util_IsDir(szDest))
 1632         _tcscat(szDest, _T("\\*.*"));
 1633 
 1634     WIN32_FIND_DATA findData;
 1635     HANDLE hSearch = FindFirstFile(szSource, &findData);
 1636     if (hSearch == INVALID_HANDLE_VALUE)
 1637     {
 1638         aLastError = GetLastError(); // Set even in this case since FindFirstFile can fail due to actual errors, such as an invalid path.
 1639         return 0; // Indicate no failures.
 1640     }
 1641     aLastError = 0; // Set default. Overridden only when a failure occurs.
 1642 
 1643     // Otherwise, loop through all the matching files.
 1644 
 1645     // Locate the filename/pattern, which will be overwritten on each iteration.
 1646     LPTSTR source_append_pos = _tcsrchr(szSource, '\\') + 1;
 1647     LPTSTR dest_append_pos = _tcsrchr(szDest, '\\') + 1;
 1648     size_t space_remaining = _countof(szSource) - (source_append_pos - szSource) - 1;
 1649 
 1650     // Copy destination filename or pattern, since it will be overwritten.
 1651     tcslcpy(szDestPattern, dest_append_pos, _countof(szDestPattern));
 1652 
 1653     int failure_count = 0;
 1654     LONG_OPERATION_INIT
 1655 
 1656     do
 1657     {
 1658         // Since other script threads can interrupt during LONG_OPERATION_UPDATE, it's important that
 1659         // this function and those that call it not refer to sArgDeref[] and sArgVar[] anytime after an
 1660         // interruption becomes possible. This is because an interrupting thread usually changes the
 1661         // values to something inappropriate for this thread.
 1662         LONG_OPERATION_UPDATE
 1663 
 1664         // Make sure the returned handle is a file and not a directory before we
 1665         // try and do copy type things on it!
 1666         if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) // dwFileAttributes should never be invalid (0xFFFFFFFF) in this case.
 1667             continue;
 1668 
 1669         if (_tcslen(findData.cFileName) > space_remaining) // v1.0.45.03: Basic check in case of files whose full spec is over 260 characters long.
 1670         {
 1671             aLastError = ERROR_BUFFER_OVERFLOW; // MSDN: "The file name is too long."
 1672             ++failure_count;
 1673             continue;
 1674         }
 1675         _tcscpy(source_append_pos, findData.cFileName); // Indirectly populate szSource. Above has ensured this won't overflow.
 1676 
 1677         // Expand the destination based on this found file
 1678         Util_ExpandFilenameWildcard(findData.cFileName, szDestPattern, dest_append_pos);
 1679 
 1680         // Fixed for v1.0.36.01: This section has been revised to avoid unnecessary calls; but more
 1681         // importantly, it now avoids the deletion and complete loss of a file when it is copied or
 1682         // moved onto itself.  That used to happen because any existing destination file used to be
 1683         // deleted prior to attempting the move/copy.
 1684         if (bMove)  // Move vs. copy mode.
 1685         {
 1686             // Note that MoveFile() is capable of moving a file to a different volume, regardless of
 1687             // operating system version.  That's enough for what we need because this function never
 1688             // moves directories, only files.
 1689 
 1690             // The following call will report success if source and dest are the same file, even if
 1691             // source is something like "..\Folder\Filename.txt" and dest is something like
 1692             // "C:\Folder\Filename.txt" (or if source is an 8.3 filename and dest is the long name
 1693             // of the same file).  This is good because it avoids the need to devise code
 1694             // to determine whether two different path names refer to the same physical file
 1695             // (note that GetFullPathName() has shown itself to be inadequate for this purpose due
 1696             // to problems with short vs. long names, UNC vs. mapped drive, and possibly NTFS hard
 1697             // links (aliases) that might all cause two different filenames to point to the same
 1698             // physical file on disk (hopefully MoveFile handles all of these correctly by indicating
 1699             // success [below] when a file is moved onto itself, though it has only been tested for
 1700             // basic cases of relative vs. absolute path).
 1701             if (!MoveFile(szSource, szDest))
 1702             {
 1703                 // If overwrite mode was not specified by the caller, or it was but the existing
 1704                 // destination file cannot be deleted (perhaps because it is a folder rather than
 1705                 // a file), or it can be deleted but the source cannot be moved, indicate a failure.
 1706                 // But by design, continue the operation.  The following relies heavily on
 1707                 // short-circuit boolean evaluation order:
 1708                 if (   !(bOverwrite && DeleteFile(szDest) && MoveFile(szSource, szDest))   )
 1709                 {
 1710                     aLastError = GetLastError();
 1711                     ++failure_count; // At this stage, any of the above 3 being false is cause for failure.
 1712                 }
 1713                 //else everything succeeded, so nothing extra needs to be done.  In either case,
 1714                 // continue on to the next file.
 1715             }
 1716         }
 1717         else // The mode is "Copy" vs. "Move"
 1718             if (!CopyFile(szSource, szDest, !bOverwrite)) // Force it to fail if bOverwrite==false.
 1719             {
 1720                 aLastError = GetLastError();
 1721                 ++failure_count;
 1722             }
 1723     } while (FindNextFile(hSearch, &findData));
 1724 
 1725     FindClose(hSearch);
 1726     return failure_count;
 1727 }
 1728 
 1729 
 1730 
 1731 void Line::Util_ExpandFilenameWildcard(LPCTSTR szSource, LPCTSTR szDest, LPTSTR szExpandedDest)
 1732 {
 1733     // copy one.two.three  *.txt     = one.two   .txt
 1734     // copy one.two.three  *.*.txt   = one.two.  .txt  (extra asterisks are removed)
 1735     // copy one.two        test      = test
 1736 
 1737     TCHAR   szSrcFile[_MAX_PATH+1];
 1738     TCHAR   szSrcExt[_MAX_PATH+1];
 1739 
 1740     TCHAR   szDestFile[_MAX_PATH+1];
 1741     TCHAR   szDestExt[_MAX_PATH+1];
 1742 
 1743     // If the destination doesn't include a wildcard, send it back verbatim
 1744     if (_tcschr(szDest, '*') == NULL)
 1745     {
 1746         _tcscpy(szExpandedDest, szDest);
 1747         return;
 1748     }
 1749 
 1750     // Split source and dest into file and extension
 1751     _tsplitpath( szSource, NULL, NULL, szSrcFile, szSrcExt );
 1752     _tsplitpath( szDest, NULL, NULL, szDestFile, szDestExt );
 1753 
 1754     // Source and Dest ext will either be ".nnnn" or "" or ".*", remove the period
 1755     if (szSrcExt[0] == '.')
 1756         _tcscpy(szSrcExt, &szSrcExt[1]);
 1757     if (szDestExt[0] == '.')
 1758         _tcscpy(szDestExt, &szDestExt[1]);
 1759 
 1760     // Replace first * in the destfile with the srcfile, remove any other *
 1761     Util_ExpandFilenameWildcardPart(szSrcFile, szDestFile, szExpandedDest);
 1762     
 1763     if (*szSrcExt || *szDestExt)
 1764     {
 1765         LPTSTR ext = _tcschr(szExpandedDest, '\0');
 1766         
 1767         if (!szDestExt[0])
 1768         {
 1769             // Always include the source extension if destination extension was blank
 1770             // (for backward-compatibility, this is done even if a '.' was present)
 1771             szDestExt[0] = '*';
 1772             szDestExt[1] = '\0';
 1773         }
 1774 
 1775         // Replace first * in the destext with the srcext, remove any other *
 1776         Util_ExpandFilenameWildcardPart(szSrcExt, szDestExt, ext + 1);
 1777 
 1778         // If there's a non-blank extension, replace the filename's null terminator with .
 1779         if (ext[1])
 1780             *ext = '.';
 1781     }
 1782 }
 1783 
 1784 
 1785 
 1786 void Line::Util_ExpandFilenameWildcardPart(LPCTSTR szSource, LPCTSTR szDest, LPTSTR szExpandedDest)
 1787 {
 1788     LPTSTR lpTemp;
 1789     int i, j, k;
 1790 
 1791     // Replace first * in the dest with the src, remove any other *
 1792     i = 0; j = 0; k = 0;
 1793     lpTemp = (LPTSTR)_tcschr(szDest, '*');
 1794     if (lpTemp != NULL)
 1795     {
 1796         // Contains at least one *, copy up to this point
 1797         while(szDest[i] != '*')
 1798             szExpandedDest[j++] = szDest[i++];
 1799         // Skip the * and replace in the dest with the srcext
 1800         while(szSource[k] != '\0')
 1801             szExpandedDest[j++] = szSource[k++];
 1802         // Skip any other *
 1803         i++;
 1804         while(szDest[i] != '\0')
 1805         {
 1806             if (szDest[i] == '*')
 1807                 i++;
 1808             else
 1809                 szExpandedDest[j++] = szDest[i++];
 1810         }
 1811         szExpandedDest[j] = '\0';
 1812     }
 1813     else
 1814     {
 1815         // No wildcard, straight copy of destext
 1816         _tcscpy(szExpandedDest, szDest);
 1817     }
 1818 }
 1819 
 1820 
 1821 
 1822 bool Line::Util_DoesFileExist(LPCTSTR szFilename)  // Returns true if file or directory exists.
 1823 {
 1824     if ( _tcschr(szFilename,'*')||_tcschr(szFilename,'?') )
 1825     {
 1826         WIN32_FIND_DATA wfd;
 1827         HANDLE          hFile;
 1828 
 1829         hFile = FindFirstFile(szFilename, &wfd);
 1830 
 1831         if ( hFile == INVALID_HANDLE_VALUE )
 1832             return false;
 1833 
 1834         FindClose(hFile);
 1835         return true;
 1836     }
 1837     else
 1838     {
 1839         DWORD dwTemp;
 1840 
 1841         dwTemp = GetFileAttributes(szFilename);
 1842         if ( dwTemp != 0xffffffff )
 1843             return true;
 1844         else
 1845             return false;
 1846     }
 1847 }
 1848 
 1849 
 1850 
 1851 bool Line::Util_IsDir(LPCTSTR szPath) // Returns true if the path is a directory
 1852 {
 1853     DWORD dwTemp = GetFileAttributes(szPath);
 1854     return dwTemp != 0xffffffff && (dwTemp & FILE_ATTRIBUTE_DIRECTORY);
 1855 }
 1856 
 1857 
 1858 
 1859 void Line::Util_GetFullPathName(LPCTSTR szIn, LPTSTR szOut)
 1860 // Returns the full pathname and strips any trailing \s.  Assumes output is _MAX_PATH in size.
 1861 {
 1862     LPTSTR szFilePart;
 1863     GetFullPathName(szIn, _MAX_PATH, szOut, &szFilePart);
 1864     strip_trailing_backslash(szOut);
 1865 }
 1866 
 1867 
 1868 
 1869 void Line::Util_GetFullPathName(LPCTSTR szIn, LPTSTR szOut, DWORD aBufSize)
 1870 {
 1871     GetFullPathName(szIn, aBufSize, szOut, NULL);
 1872     strip_trailing_backslash(szOut);
 1873 }
 1874 
 1875 
 1876 
 1877 bool Util_Shutdown(int nFlag)
 1878 // Shutdown or logoff the system.
 1879 // Returns false if the function could not get the rights to shutdown.
 1880 {
 1881 /* 
 1882 flags can be a combination of:
 1883 #define EWX_LOGOFF           0
 1884 #define EWX_SHUTDOWN         0x00000001
 1885 #define EWX_REBOOT           0x00000002
 1886 #define EWX_FORCE            0x00000004
 1887 #define EWX_POWEROFF         0x00000008 */
 1888 
 1889     HANDLE              hToken; 
 1890     TOKEN_PRIVILEGES    tkp; 
 1891 
 1892     // Get a token for this process.
 1893     if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) 
 1894         return false;                       // Don't have the rights
 1895  
 1896     // Get the LUID for the shutdown privilege.
 1897     LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid); 
 1898  
 1899     tkp.PrivilegeCount = 1;  /* one privilege to set */
 1900     tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 
 1901  
 1902     // Get the shutdown privilege for this process.
 1903     AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0); 
 1904  
 1905     // Cannot test the return value of AdjustTokenPrivileges.
 1906     if (GetLastError() != ERROR_SUCCESS) 
 1907         return false;                       // Don't have the rights
 1908 
 1909     // ExitWindows
 1910     if (ExitWindowsEx(nFlag, 0))
 1911         return true;
 1912     else
 1913         return false;
 1914 
 1915 }
 1916 
 1917 
 1918 
 1919 BOOL Util_ShutdownHandler(HWND hwnd, DWORD lParam)
 1920 {
 1921     // if the window is me, don't terminate!
 1922     if (hwnd != g_hWnd && hwnd != g_hWndSplash)
 1923         Util_WinKill(hwnd);
 1924 
 1925     // Continue the enumeration.
 1926     return TRUE;
 1927 
 1928 }
 1929 
 1930 
 1931 
 1932 void Util_WinKill(HWND hWnd)
 1933 {
 1934     DWORD_PTR dwResult;
 1935     // Use WM_CLOSE vs. SC_CLOSE in this case, since the target window is slightly more likely to
 1936     // respond to that:
 1937     if (!SendMessageTimeout(hWnd, WM_CLOSE, 0, 0, SMTO_ABORTIFHUNG, 500, &dwResult)) // Wait up to 500ms.
 1938     {
 1939         // Use more force - Mwuahaha
 1940         DWORD pid = 0;
 1941         GetWindowThreadProcessId(hWnd, &pid);
 1942         HANDLE hProcess = pid ? OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) : NULL;
 1943         if (hProcess)
 1944         {
 1945             TerminateProcess(hProcess, 0);
 1946             CloseHandle(hProcess);
 1947         }
 1948     }
 1949 }
 1950 
 1951 
 1952 
 1953 void DoIncrementalMouseMove(int aX1, int aY1, int aX2, int aY2, int aSpeed)
 1954 // aX1 and aY1 are the starting coordinates, and "2" are the destination coordinates.
 1955 // Caller has ensured that aSpeed is in the range 0 to 100, inclusive.
 1956 {
 1957     // AutoIt3: So, it's a more gradual speed that is needed :)
 1958     int delta;
 1959     #define INCR_MOUSE_MIN_SPEED 32
 1960 
 1961     while (aX1 != aX2 || aY1 != aY2)
 1962     {
 1963         if (aX1 < aX2)
 1964         {
 1965             delta = (aX2 - aX1) / aSpeed;
 1966             if (delta == 0 || delta < INCR_MOUSE_MIN_SPEED)
 1967                 delta = INCR_MOUSE_MIN_SPEED;
 1968             if ((aX1 + delta) > aX2)
 1969                 aX1 = aX2;
 1970             else
 1971                 aX1 += delta;
 1972         } 
 1973         else 
 1974             if (aX1 > aX2)
 1975             {
 1976                 delta = (aX1 - aX2) / aSpeed;
 1977                 if (delta == 0 || delta < INCR_MOUSE_MIN_SPEED)
 1978                     delta = INCR_MOUSE_MIN_SPEED;
 1979                 if ((aX1 - delta) < aX2)
 1980                     aX1 = aX2;
 1981                 else
 1982                     aX1 -= delta;
 1983             }
 1984 
 1985         if (aY1 < aY2)
 1986         {
 1987             delta = (aY2 - aY1) / aSpeed;
 1988             if (delta == 0 || delta < INCR_MOUSE_MIN_SPEED)
 1989                 delta = INCR_MOUSE_MIN_SPEED;
 1990             if ((aY1 + delta) > aY2)
 1991                 aY1 = aY2;
 1992             else
 1993                 aY1 += delta;
 1994         } 
 1995         else 
 1996             if (aY1 > aY2)
 1997             {
 1998                 delta = (aY1 - aY2) / aSpeed;
 1999                 if (delta == 0 || delta < INCR_MOUSE_MIN_SPEED)
 2000                     delta = INCR_MOUSE_MIN_SPEED;
 2001                 if ((aY1 - delta) < aY2)
 2002                     aY1 = aY2;
 2003                 else
 2004                     aY1 -= delta;
 2005             }
 2006 
 2007         MouseEvent(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, 0, aX1, aY1);
 2008         DoMouseDelay();
 2009         // Above: A delay is required for backward compatibility and because it's just how the incremental-move
 2010         // feature was originally designed in AutoIt v3.  It may in fact improve reliability in some cases,
 2011         // especially with the mouse_event() method vs. SendInput/Play.
 2012     } // while()
 2013 }
 2014 
 2015 
 2016 
 2017 ////////////////////
 2018 // PROCESS ROUTINES
 2019 ////////////////////
 2020 
 2021 DWORD ProcessExist(LPTSTR aProcess)
 2022 {
 2023     PROCESSENTRY32 proc;
 2024     proc.dwSize = sizeof(proc);
 2025     HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
 2026     Process32First(snapshot, &proc);
 2027 
 2028     // Determine the PID if aProcess is a pure, non-negative integer (any negative number
 2029     // is more likely to be the name of a process [with a leading dash], rather than the PID).
 2030     DWORD specified_pid = IsPureNumeric(aProcess) ? ATOU(aProcess) : 0;
 2031     TCHAR szDrive[_MAX_PATH+1], szDir[_MAX_PATH+1], szFile[_MAX_PATH+1], szExt[_MAX_PATH+1];
 2032 
 2033     while (Process32Next(snapshot, &proc))
 2034     {
 2035         if (specified_pid && specified_pid == proc.th32ProcessID)
 2036         {
 2037             CloseHandle(snapshot);
 2038             return specified_pid;
 2039         }
 2040         // Otherwise, check for matching name even if aProcess is purely numeric (i.e. a number might
 2041         // also be a valid name?):
 2042         // It seems that proc.szExeFile never contains a path, just the executable name.
 2043         // But in case it ever does, ensure consistency by removing the path:
 2044         _tsplitpath(proc.szExeFile, szDrive, szDir, szFile, szExt);
 2045         _tcscat(szFile, szExt);
 2046         if (!_tcsicmp(szFile, aProcess)) // lstrcmpi() is not used: 1) avoids breaking existing scripts; 2) provides consistent behavior across multiple locales; 3) performance.
 2047         {
 2048             CloseHandle(snapshot);
 2049             return proc.th32ProcessID;
 2050         }
 2051     }
 2052     CloseHandle(snapshot);
 2053     return 0;  // Not found.
 2054 }