home *** CD-ROM | disk | FTP | other *** search
/ Computer Shopper 242 / Issue 242 - April 2008 - DPCS0408DVD.ISO / Open Source / AutoHotKey / Source / AutoHotkey104705_source.exe / source / script2.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2007-11-20  |  785.1 KB  |  16,061 lines

  1. /*
  2. AutoHotkey
  3.  
  4. Copyright 2003-2007 Chris Mallett (support@autohotkey.com)
  5.  
  6. This program is free software; you can redistribute it and/or
  7. modify it under the terms of the GNU General Public License
  8. as published by the Free Software Foundation; either version 2
  9. of the License, or (at your option) any later version.
  10.  
  11. This program is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. GNU General Public License for more details.
  15. */
  16.  
  17. #include "stdafx.h" // pre-compiled headers
  18. #include <olectl.h> // for OleLoadPicture()
  19. #include <winioctl.h> // For PREVENT_MEDIA_REMOVAL and CD lock/unlock.
  20. #include "qmath.h" // Used by Transform() [math.h incurs 2k larger code size just for ceil() & floor()]
  21. #include "mt19937ar-cok.h" // for sorting in random order
  22. #include "script.h"
  23. #include "window.h" // for IF_USE_FOREGROUND_WINDOW
  24. #include "application.h" // for MsgSleep()
  25. #include "resources\resource.h"  // For InputBox.
  26.  
  27. #define PCRE_STATIC             // For RegEx. PCRE_STATIC tells PCRE to declare its functions for normal, static
  28. #include "lib_pcre/pcre/pcre.h" // linkage rather than as functions inside an external DLL.
  29.  
  30.  
  31. ////////////////////
  32. // Window related //
  33. ////////////////////
  34.  
  35. ResultType Line::Splash(char *aOptions, char *aSubText, char *aMainText, char *aTitle, char *aFontName
  36.     , char *aImageFile, bool aSplashImage)
  37. {
  38.     int window_index = 0;  // Set the default window to operate upon (the first).
  39.     char *options, *image_filename = aImageFile;  // Set default.
  40.     bool turn_off = false;
  41.     bool show_it_only = false;
  42.     int bar_pos;
  43.     bool bar_pos_has_been_set = false;
  44.     bool options_consist_of_bar_pos_only = false;
  45.  
  46.     if (aSplashImage)
  47.     {
  48.         options = aOptions;
  49.         if (*aImageFile)
  50.         {
  51.             char *colon_pos = strchr(aImageFile, ':');
  52.             char *image_filename_omit_leading_whitespace = omit_leading_whitespace(image_filename); // Added in v1.0.38.04 per someone's suggestion.
  53.             if (colon_pos)
  54.             {
  55.                 char window_number_str[32];  // Allow extra room in case leading spaces or in hex format, e.g. 0x02
  56.                 size_t length_to_copy = colon_pos - aImageFile;
  57.                 if (length_to_copy < sizeof(window_number_str))
  58.                 {
  59.                     strlcpy(window_number_str, aImageFile, length_to_copy + 1);
  60.                     if (IsPureNumeric(window_number_str, false, false, true)) // Seems best to allow float at runtime.
  61.                     {
  62.                         // Note that filenames can start with spaces, so omit_leading_whitespace() is only
  63.                         // used if the string is entirely blank:
  64.                         image_filename = colon_pos + 1;
  65.                         image_filename_omit_leading_whitespace = omit_leading_whitespace(image_filename); // Update to reflect the change above.
  66.                         if (!*image_filename_omit_leading_whitespace)
  67.                             image_filename = image_filename_omit_leading_whitespace;
  68.                         window_index = ATOI(window_number_str) - 1;
  69.                         if (window_index < 0 || window_index >= MAX_SPLASHIMAGE_WINDOWS)
  70.                             return LineError("Max window number is " MAX_SPLASHIMAGE_WINDOWS_STR "." ERR_ABORT
  71.                                 , FAIL, aOptions);
  72.                     }
  73.                 }
  74.             }
  75.             if (!stricmp(image_filename_omit_leading_whitespace, "Off")) // v1.0.38.04: Ignores leading whitespace per someone's suggestion.
  76.                 turn_off = true;
  77.             else if (!stricmp(image_filename_omit_leading_whitespace, "Show")) // v1.0.38.04: Ignores leading whitespace per someone's suggestion.
  78.                 show_it_only = true;
  79.         }
  80.     }
  81.     else // Progress Window.
  82.     {
  83.         if (   !(options = strchr(aOptions, ':'))   )
  84.             options = aOptions;
  85.         else
  86.         {
  87.             window_index = ATOI(aOptions) - 1;
  88.             if (window_index < 0 || window_index >= MAX_PROGRESS_WINDOWS)
  89.                 return LineError("Max window number is " MAX_PROGRESS_WINDOWS_STR "." ERR_ABORT, FAIL, aOptions);
  90.             ++options;
  91.         }
  92.         options = omit_leading_whitespace(options); // Added in v1.0.38.04 per someone's suggestion.
  93.         if (!stricmp(options, "Off"))
  94.             turn_off = true;
  95.         else if (!stricmp(options, "Show"))
  96.             show_it_only = true;
  97.         else
  98.         {
  99.             // Allow floats at runtime for flexibility (i.e. in case aOptions was in a variable reference).
  100.             // But still use ATOI for the conversion:
  101.             if (IsPureNumeric(options, true, false, true)) // Negatives are allowed as of v1.0.25.
  102.             {
  103.                 bar_pos = ATOI(options);
  104.                 bar_pos_has_been_set = true;
  105.                 options_consist_of_bar_pos_only = true;
  106.             }
  107.             //else leave it set to the default.
  108.         }
  109.     }
  110.  
  111.     SplashType &splash = aSplashImage ? g_SplashImage[window_index] : g_Progress[window_index];
  112.  
  113.     // In case it's possible for the window to get destroyed by other means (WinClose?).
  114.     // Do this only after the above options were set so that the each window's settings
  115.     // will be remembered until such time as "Command, Off" is used:
  116.     if (splash.hwnd && !IsWindow(splash.hwnd))
  117.         splash.hwnd = NULL;
  118.  
  119.     if (show_it_only)
  120.     {
  121.         if (splash.hwnd && !IsWindowVisible(splash.hwnd))
  122.             ShowWindow(splash.hwnd,  SW_SHOWNOACTIVATE); // See bottom of this function for comments on SW_SHOWNOACTIVATE.
  123.         //else for simplicity, do nothing.
  124.         return OK;
  125.     }
  126.  
  127.     if (!turn_off && splash.hwnd && !*image_filename && (options_consist_of_bar_pos_only || !*options)) // The "modify existing window" mode is in effect.
  128.     {
  129.         // If there is an existing window, just update its bar position and text.
  130.         // If not, do nothing since we don't have the original text of the window to recreate it.
  131.         // Since this is our thread's window, it shouldn't be necessary to use SendMessageTimeout()
  132.         // since the window cannot be hung since by definition our thread isn't hung.  Also, setting
  133.         // a text item from non-blank to blank is not supported so that elements can be omitted from an
  134.         // update command without changing the text that's in the window.  The script can specify %a_space%
  135.         // to explicitly make an element blank.
  136.         if (!aSplashImage && bar_pos_has_been_set && splash.bar_pos != bar_pos) // Avoid unnecessary redrawing.
  137.         {
  138.             splash.bar_pos = bar_pos;
  139.             if (splash.hwnd_bar)
  140.                 SendMessage(splash.hwnd_bar, PBM_SETPOS, (WPARAM)bar_pos, 0);
  141.         }
  142.         // SendMessage() vs. SetWindowText() is used for controls so that tabs are expanded.
  143.         // For simplicity, the hwnd_text1 control is not expanded dynamically if it is currently of
  144.         // height zero.  The user can recreate the window if a different height is needed.
  145.         if (*aMainText && splash.hwnd_text1)
  146.             SendMessage(splash.hwnd_text1, WM_SETTEXT, 0, (LPARAM)(aMainText));
  147.         if (*aSubText)
  148.             SendMessage(splash.hwnd_text2, WM_SETTEXT, 0, (LPARAM)(aSubText));
  149.         if (*aTitle)
  150.             SetWindowText(splash.hwnd, aTitle); // Use the simple method for parent window titles.
  151.         return OK;
  152.     }
  153.  
  154.     // Otherwise, destroy any existing window first:
  155.     if (splash.hwnd)
  156.         DestroyWindow(splash.hwnd);
  157.     if (splash.hfont1) // Destroy font only after destroying the window that uses it.
  158.         DeleteObject(splash.hfont1);
  159.     if (splash.hfont2)
  160.         DeleteObject(splash.hfont2);
  161.     if (splash.hbrush)
  162.         DeleteObject(splash.hbrush);
  163.     if (splash.pic)
  164.         splash.pic->Release();
  165.     ZeroMemory(&splash, sizeof(splash)); // Set the above and all other fields to zero.
  166.  
  167.     if (turn_off)
  168.         return OK;
  169.     // Otherwise, the window needs to be created or recreated.
  170.  
  171.     if (!*aTitle) // Provide default title.
  172.         aTitle = (g_script.mFileName && *g_script.mFileName) ? g_script.mFileName : "";
  173.  
  174.     // Since there is often just one progress/splash window, and it defaults to always-on-top,
  175.     // it seems best to default owned to be true so that it doesn't get its own task bar button:
  176.     bool owned = true;          // Whether this window is owned by the main window.
  177.     bool centered_main = true;  // Whether the main text is centered.
  178.     bool centered_sub = true;   // Whether the sub text is centered.
  179.     bool initially_hidden = false;  // Whether the window should kept hidden (for later showing by the script).
  180.     int style = WS_DISABLED|WS_POPUP|WS_CAPTION;  // WS_CAPTION implies WS_BORDER
  181.     int exstyle = WS_EX_TOPMOST;
  182.     int xpos = COORD_UNSPECIFIED;
  183.     int ypos = COORD_UNSPECIFIED;
  184.     int range_min = 0, range_max = 0;  // For progress bars.
  185.     int font_size1 = 0; // 0 is the flag to "use default size".
  186.     int font_size2 = 0;
  187.     int font_weight1 = FW_DONTCARE;  // Flag later logic to use default.
  188.     int font_weight2 = FW_DONTCARE;  // Flag later logic to use default.
  189.     COLORREF bar_color = CLR_DEFAULT;
  190.     splash.color_bk = CLR_DEFAULT;
  191.     splash.color_text = CLR_DEFAULT;
  192.     splash.height = COORD_UNSPECIFIED;
  193.     if (aSplashImage)
  194.     {
  195.         #define SPLASH_DEFAULT_WIDTH 300
  196.         splash.width = COORD_UNSPECIFIED;
  197.         splash.object_height = COORD_UNSPECIFIED;
  198.     }
  199.     else // Progress window.
  200.     {
  201.         splash.width = SPLASH_DEFAULT_WIDTH;
  202.         splash.object_height = 20;
  203.     }
  204.     splash.object_width = COORD_UNSPECIFIED;  // Currently only used for SplashImage, not Progress.
  205.     if (*aMainText || *aSubText || !aSplashImage)
  206.     {
  207.         splash.margin_x = 10;
  208.         splash.margin_y = 5;
  209.     }
  210.     else // Displaying only a naked image, so don't use borders.
  211.         splash.margin_x = splash.margin_y = 0;
  212.  
  213.     for (char *cp2, *cp = options; *cp; ++cp)
  214.     {
  215.         switch(toupper(*cp))
  216.         {
  217.         case 'A':  // Non-Always-on-top.  Synonymous with A0 in early versions.
  218.             // Decided against this enforcement.  In the enhancement mentioned below is ever done (unlikely),
  219.             // it seems that A1 can turn always-on-top on and A0 or A by itself can turn it off:
  220.             //if (cp[1] == '0') // The zero is required to allow for future enhancement: modify attrib. of existing window.
  221.             exstyle &= ~WS_EX_TOPMOST;
  222.             break;
  223.         case 'B': // Borderless and/or Titleless
  224.             style &= ~WS_CAPTION;
  225.             if (cp[1] == '1')
  226.                 style |= WS_BORDER;
  227.             else if (cp[1] == '2')
  228.                 style |= WS_DLGFRAME;
  229.             break;
  230.         case 'C': // Colors
  231.             if (!cp[1]) // Avoids out-of-bounds when the loop's own ++cp is done.
  232.                 break;
  233.             ++cp; // Always increment to omit the next char from consideration by the next loop iteration.
  234.             switch(toupper(*cp))
  235.             {
  236.             case 'B': // Bar color.
  237.             case 'T': // Text color.
  238.             case 'W': // Window/Background color.
  239.             {
  240.                 char color_str[32];
  241.                 strlcpy(color_str, cp + 1, sizeof(color_str));
  242.                 char *space_pos = StrChrAny(color_str, " \t");  // space or tab
  243.                 if (space_pos)
  244.                     *space_pos = '\0';
  245.                 //else a color name can still be present if it's at the end of the string.
  246.                 COLORREF color = ColorNameToBGR(color_str);
  247.                 if (color == CLR_NONE) // A matching color name was not found, so assume it's in hex format.
  248.                 {
  249.                     if (strlen(color_str) > 6)
  250.                         color_str[6] = '\0';  // Shorten to exactly 6 chars, which happens if no space/tab delimiter is present.
  251.                     color = rgb_to_bgr(strtol(color_str, NULL, 16));
  252.                     // if color_str does not contain something hex-numeric, black (0x00) will be assumed,
  253.                     // which seems okay given how rare such a problem would be.
  254.                 }
  255.                 switch (toupper(*cp))
  256.                 {
  257.                 case 'B':
  258.                     bar_color = color;
  259.                     break;
  260.                 case 'T':
  261.                     splash.color_text = color;
  262.                     break;
  263.                 case 'W':
  264.                     splash.color_bk = color;
  265.                     splash.hbrush = CreateSolidBrush(color); // Used for window & control backgrounds.
  266.                     break;
  267.                 }
  268.                 // Skip over the color string to avoid interpreting hex digits or color names as option letters:
  269.                 cp += strlen(color_str);
  270.                 break;
  271.             }
  272.             default:
  273.                 centered_sub = (*cp != '0');
  274.                 centered_main = (cp[1] != '0');
  275.             }
  276.         case 'F':
  277.             if (!cp[1]) // Avoids out-of-bounds when the loop's own ++cp is done.
  278.                 break;
  279.             ++cp; // Always increment to omit the next char from consideration by the next loop iteration.
  280.             switch(toupper(*cp))
  281.             {
  282.             case 'M':
  283.                 if ((font_size1 = atoi(cp + 1)) < 0)
  284.                     font_size1 = 0;
  285.                 break;
  286.             case 'S':
  287.                 if ((font_size2 = atoi(cp + 1)) < 0)
  288.                     font_size2 = 0;
  289.                 break;
  290.             }
  291.             break;
  292.         case 'M': // Movable and (optionally) resizable.
  293.             style &= ~WS_DISABLED;
  294.             if (cp[1] == '1')
  295.                 style |= WS_SIZEBOX;
  296.             if (cp[1] == '2')
  297.                 style |= WS_SIZEBOX|WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_SYSMENU;
  298.             break;
  299.         case 'P': // Starting position of progress bar [v1.0.25]
  300.             bar_pos = atoi(cp + 1);
  301.             bar_pos_has_been_set = true;
  302.             break;
  303.         case 'R': // Range of progress bar [v1.0.25]
  304.             if (!cp[1]) // Ignore it because we don't want cp to ever point to the NULL terminator due to the loop's increment.
  305.                 break;
  306.             range_min = ATOI(++cp); // Increment cp to point it to range_min.
  307.             if (cp2 = strchr(cp + 1, '-'))  // +1 to omit the min's minus sign, if it has one.
  308.             {
  309.                 cp = cp2;
  310.                 if (!cp[1]) // Ignore it because we don't want cp to ever point to the NULL terminator due to the loop's increment.
  311.                     break;
  312.                 range_max = ATOI(++cp); // Increment cp to point it to range_max, which can be negative as in this example: R-100--50
  313.             }
  314.             break;
  315.         case 'T': // Give it a task bar button by making it a non-owned window.
  316.             owned = false;
  317.             break;
  318.         // For options such as W, X and Y:
  319.         // Use atoi() vs. ATOI() to avoid interpreting something like 0x01B as hex when in fact
  320.         // the B was meant to be an option letter:
  321.         case 'W':
  322.             if (!cp[1]) // Avoids out-of-bounds when the loop's own ++cp is done.
  323.                 break;
  324.             ++cp; // Always increment to omit the next char from consideration by the next loop iteration.
  325.             switch(toupper(*cp))
  326.             {
  327.             case 'M':
  328.                 if ((font_weight1 = atoi(cp + 1)) < 0)
  329.                     font_weight1 = 0;
  330.                 break;
  331.             case 'S':
  332.                 if ((font_weight2 = atoi(cp + 1)) < 0)
  333.                     font_weight2 = 0;
  334.                 break;
  335.             default:
  336.                 splash.width = atoi(cp);
  337.             }
  338.             break;
  339.         case 'H':
  340.             if (!strnicmp(cp, "Hide", 4)) // Hide vs. Hidden is debatable.
  341.             {
  342.                 initially_hidden = true;
  343.                 cp += 3; // +3 vs. +4 due to the loop's own ++cp.
  344.             }
  345.             else // Allow any width/height to be specified so that the window can be "rolled up" to its title bar:
  346.                 splash.height = atoi(cp + 1);
  347.             break;
  348.         case 'X':
  349.             xpos = atoi(cp + 1);
  350.             break;
  351.         case 'Y':
  352.             ypos = atoi(cp + 1);
  353.             break;
  354.         case 'Z':
  355.             if (!cp[1]) // Avoids out-of-bounds when the loop's own ++cp is done.
  356.                 break;
  357.             ++cp; // Always increment to omit the next char from consideration by the next loop iteration.
  358.             switch(toupper(*cp))
  359.             {
  360.             case 'B':  // for backward compatibility with interim releases of v1.0.14
  361.             case 'H':
  362.                 splash.object_height = atoi(cp + 1); // Allow it to be zero or negative to omit the object.
  363.                 break;
  364.             case 'W':
  365.                 if (aSplashImage)
  366.                     splash.object_width = atoi(cp + 1); // Allow it to be zero or negative to omit the object.
  367.                 //else for Progress, don't allow width to be changed since a zero would omit the bar.
  368.                 break;
  369.             case 'X':
  370.                 splash.margin_x = atoi(cp + 1);
  371.                 break;
  372.             case 'Y':
  373.                 splash.margin_y = atoi(cp + 1);
  374.                 break;
  375.             }
  376.             break;
  377.         // Otherwise: Ignore other characters, such as the digits that occur after the P/X/Y option letters.
  378.         } // switch()
  379.     } // for()
  380.  
  381.     HDC hdc = CreateDC("DISPLAY", NULL, NULL, NULL);
  382.     int pixels_per_point_y = GetDeviceCaps(hdc, LOGPIXELSY);
  383.  
  384.     // Get name and size of default font.
  385.     HFONT hfont_default = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
  386.     HFONT hfont_old = (HFONT)SelectObject(hdc, hfont_default);
  387.     char default_font_name[65];
  388.     GetTextFace(hdc, sizeof(default_font_name) - 1, default_font_name);
  389.     TEXTMETRIC tm;
  390.     GetTextMetrics(hdc, &tm);
  391.     int default_gui_font_height = tm.tmHeight;
  392.  
  393.     // If both are zero or less, reset object height/width for maintainability and sizing later.
  394.     // However, if one of them is -1, the caller is asking for that dimension to be auto-calc'd
  395.     // to "keep aspect ratio" with the the other specified dimension:
  396.     if (   splash.object_height < 1 && splash.object_height != COORD_UNSPECIFIED
  397.         && splash.object_width < 1 && splash.object_width != COORD_UNSPECIFIED
  398.         || !splash.object_height || !splash.object_width   )
  399.         splash.object_height = splash.object_width = 0;
  400.  
  401.     // If there's an image, handle it first so that automatic-width can be applied (if no width was specified)
  402.     // for later font calculations:
  403.     HANDLE hfile_image;
  404.     if (aSplashImage && *image_filename && splash.object_height
  405.         && (hfile_image = CreateFile(image_filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL)) != INVALID_HANDLE_VALUE)
  406.     {
  407.         // If any of these calls fail (rare), just omit the picture from the window.
  408.         DWORD file_size = GetFileSize(hfile_image, NULL);
  409.         if (file_size != -1)
  410.         {
  411.             HGLOBAL hglobal = GlobalAlloc(GMEM_MOVEABLE, file_size); // MSDN: alloc memory based on file size
  412.             if (hglobal)
  413.             {
  414.                 LPVOID pdata = GlobalLock(hglobal);
  415.                 if (pdata)
  416.                 {
  417.                     DWORD bytes_to_read = 0;
  418.                     // MSDN: read file and store in global memory:
  419.                     if (ReadFile(hfile_image, pdata, file_size, &bytes_to_read, NULL))
  420.                     {
  421.                         // MSDN: create IStream* from global memory:
  422.                         LPSTREAM pstm = NULL;
  423.                         if (SUCCEEDED(CreateStreamOnHGlobal(hglobal, TRUE, &pstm)) && pstm)
  424.                         {
  425.                             // MSDN: Create IPicture from image file:
  426.                             if (FAILED(OleLoadPicture(pstm, file_size, FALSE, IID_IPicture, (LPVOID *)&splash.pic)))
  427.                                 splash.pic = NULL;
  428.                             pstm->Release();
  429.                             long hm_width, hm_height;
  430.                             if (splash.object_height == -1 && splash.object_width > 0)
  431.                             {
  432.                                 // Caller wants height calculated based on the specified width (keep aspect ratio).
  433.                                 splash.pic->get_Width(&hm_width);
  434.                                 splash.pic->get_Height(&hm_height);
  435.                                 if (hm_width) // Avoid any chance of divide-by-zero.
  436.                                     splash.object_height = (int)(((double)hm_height / hm_width) * splash.object_width + .5); // Round.
  437.                             }
  438.                             else if (splash.object_width == -1 && splash.object_height > 0)
  439.                             {
  440.                                 // Caller wants width calculated based on the specified height (keep aspect ratio).
  441.                                 splash.pic->get_Width(&hm_width);
  442.                                 splash.pic->get_Height(&hm_height);
  443.                                 if (hm_height) // Avoid any chance of divide-by-zero.
  444.                                     splash.object_width = (int)(((double)hm_width / hm_height) * splash.object_height + .5); // Round.
  445.                             }
  446.                             else
  447.                             {
  448.                                 if (splash.object_height == COORD_UNSPECIFIED)
  449.                                 {
  450.                                     splash.pic->get_Height(&hm_height);
  451.                                     // Convert himetric to pixels:
  452.                                     splash.object_height = MulDiv(hm_height, pixels_per_point_y, HIMETRIC_INCH);
  453.                                 }
  454.                                 if (splash.object_width == COORD_UNSPECIFIED)
  455.                                 {
  456.                                     splash.pic->get_Width(&hm_width);
  457.                                     splash.object_width = MulDiv(hm_width, GetDeviceCaps(hdc, LOGPIXELSX), HIMETRIC_INCH);
  458.                                 }
  459.                             }
  460.                             if (splash.width == COORD_UNSPECIFIED)
  461.                                 splash.width = splash.object_width + (2 * splash.margin_x);
  462.                         }
  463.                     }
  464.                     GlobalUnlock(hglobal);
  465.                 }
  466.             }
  467.         }
  468.         CloseHandle(hfile_image);
  469.     }
  470.  
  471.     // If width is still unspecified -- which should only happen if it's a SplashImage window with
  472.     // no image, or there was a problem getting the image above -- set it to be the default.
  473.     if (splash.width == COORD_UNSPECIFIED)
  474.         splash.width = SPLASH_DEFAULT_WIDTH;
  475.     // Similarly, object_height is set to zero if the object is not present:
  476.     if (splash.object_height == COORD_UNSPECIFIED)
  477.         splash.object_height = 0;
  478.  
  479.     // Lay out client area.  If height is COORD_UNSPECIFIED, use a temp value for now until
  480.     // it can be later determined.
  481.     RECT client_rect, draw_rect;
  482.     SetRect(&client_rect, 0, 0, splash.width, splash.height == COORD_UNSPECIFIED ? 500 : splash.height);
  483.  
  484.     // Create fonts based on specified point sizes.  A zero value for font_size1 & 2 are correctly handled
  485.     // by CreateFont():
  486.     if (*aMainText)
  487.     {
  488.         // If a zero size is specified, it should use the default size.  But the default brought about
  489.         // by passing a zero here is not what the system uses as a default, so instead use a font size
  490.         // that is 25% larger than the default size (since the default size itself is used for aSubtext).
  491.         // On a typical system, the default GUI font's point size is 8, so this will make it 10 by default.
  492.         // Also, it appears that changing the system's font size in Control Panel -> Display -> Appearance
  493.         // does not affect the reported default font size.  Thus, the default is probably 8/9 for most/all
  494.         // XP systems and probably other OSes as well.
  495.         // By specifying PROOF_QUALITY the nearest matching font size should be chosen, which should avoid
  496.         // any scaling artifacts that might be caused if default_gui_font_height is not 8.
  497.         if (   !(splash.hfont1 = CreateFont(font_size1 ? -MulDiv(font_size1, pixels_per_point_y, 72) : (int)(1.25 * default_gui_font_height)
  498.             , 0, 0, 0, font_weight1 ? font_weight1 : FW_SEMIBOLD, 0, 0, 0, DEFAULT_CHARSET, OUT_TT_PRECIS
  499.             , CLIP_DEFAULT_PRECIS, PROOF_QUALITY, FF_DONTCARE, *aFontName ? aFontName : default_font_name))   )
  500.             // Call it again with default font in case above failed due to non-existent aFontName.
  501.             // Update: I don't think this actually does any good, at least on XP, because it appears
  502.             // that CreateFont() does not fail merely due to a non-existent typeface.  But it is kept
  503.             // in case it ever fails for other reasons:
  504.             splash.hfont1 = CreateFont(font_size1 ? -MulDiv(font_size1, pixels_per_point_y, 72) : (int)(1.25 * default_gui_font_height)
  505.                 , 0, 0, 0, font_weight1 ? font_weight1 : FW_SEMIBOLD, 0, 0, 0, DEFAULT_CHARSET, OUT_TT_PRECIS
  506.                 , CLIP_DEFAULT_PRECIS, PROOF_QUALITY, FF_DONTCARE, default_font_name);
  507.         // To avoid showing a runtime error, fall back to the default font if other font wasn't created:
  508.         SelectObject(hdc, splash.hfont1 ? splash.hfont1 : hfont_default);
  509.         // Calc height of text by taking into account font size, number of lines, and space between lines:
  510.         draw_rect = client_rect;
  511.         draw_rect.left += splash.margin_x;
  512.         draw_rect.right -= splash.margin_x;
  513.         splash.text1_height = DrawText(hdc, aMainText, -1, &draw_rect, DT_CALCRECT | DT_WORDBREAK | DT_EXPANDTABS);
  514.     }
  515.     // else leave the above fields set to the zero defaults.
  516.  
  517.     if (font_size2 || font_weight2 || aFontName)
  518.         if (   !(splash.hfont2 = CreateFont(-MulDiv(font_size2, pixels_per_point_y, 72), 0, 0, 0
  519.             , font_weight2, 0, 0, 0, DEFAULT_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS
  520.             , PROOF_QUALITY, FF_DONTCARE, *aFontName ? aFontName : default_font_name))   )
  521.             // Call it again with default font in case above failed due to non-existent aFontName.
  522.             // Update: I don't think this actually does any good, at least on XP, because it appears
  523.             // that CreateFont() does not fail merely due to a non-existent typeface.  But it is kept
  524.             // in case it ever fails for other reasons:
  525.             if (font_size2 || font_weight2)
  526.                 splash.hfont2 = CreateFont(-MulDiv(font_size2, pixels_per_point_y, 72), 0, 0, 0
  527.                     , font_weight2, 0, 0, 0, DEFAULT_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS
  528.                     , PROOF_QUALITY, FF_DONTCARE, default_font_name);
  529.     //else leave it NULL so that hfont_default will be used.
  530.  
  531.     // The font(s) will be deleted the next time this window is destroyed or recreated,
  532.     // or by the g_script destructor.
  533.  
  534.     SPLASH_CALC_YPOS  // Calculate the Y position of each control in the window.
  535.  
  536.     if (splash.height == COORD_UNSPECIFIED)
  537.     {
  538.         // Since the window height was not specified, determine what it should be based on the height
  539.         // of all the controls in the window:
  540.         int subtext_height;
  541.         if (*aSubText)
  542.         {
  543.             SelectObject(hdc, splash.hfont2 ? splash.hfont2 : hfont_default);
  544.             // Calc height of text by taking into account font size, number of lines, and space between lines:
  545.             // Reset unconditionally because the previous DrawText() sometimes alters the rect:
  546.             draw_rect = client_rect;
  547.             draw_rect.left += splash.margin_x;
  548.             draw_rect.right -= splash.margin_x;
  549.             subtext_height = DrawText(hdc, aSubText, -1, &draw_rect, DT_CALCRECT | DT_WORDBREAK);
  550.         }
  551.         else
  552.             subtext_height = 0;
  553.         // For the below: sub_y was previously calc'd to be the top of the subtext control.
  554.         // Also, splash.margin_y is added because the text looks a little better if the window
  555.         // doesn't end immediately beneath it:
  556.         splash.height = subtext_height + sub_y + splash.margin_y;
  557.         client_rect.bottom = splash.height;
  558.     }
  559.  
  560.     SelectObject(hdc, hfont_old); // Necessary to avoid memory leak.
  561.     if (!DeleteDC(hdc))
  562.         return FAIL;  // Force a failure to detect bugs such as hdc still having a created handle inside.
  563.  
  564.     // Based on the client area determined above, expand the main_rect to include title bar, borders, etc.
  565.     // If the window has a border or caption this also changes top & left *slightly* from zero.
  566.     RECT main_rect = client_rect;
  567.     AdjustWindowRectEx(&main_rect, style, FALSE, exstyle);
  568.     int main_width = main_rect.right - main_rect.left;  // main.left might be slightly less than zero.
  569.     int main_height = main_rect.bottom - main_rect.top; // main.top might be slightly less than zero.
  570.  
  571.     RECT work_rect;
  572.     SystemParametersInfo(SPI_GETWORKAREA, 0, &work_rect, 0);  // Get desktop rect excluding task bar.
  573.     int work_width = work_rect.right - work_rect.left;  // Note that "left" won't be zero if task bar is on left!
  574.     int work_height = work_rect.bottom - work_rect.top;  // Note that "top" won't be zero if task bar is on top!
  575.  
  576.     // Seems best (and easier) to unconditionally restrict window size to the size of the desktop,
  577.     // since most users would probably want that.  This can be overridden by using WinMove afterward.
  578.     if (main_width > work_width)
  579.         main_width = work_width;
  580.     if (main_height > work_height)
  581.         main_height = work_height;
  582.  
  583.     // Centering doesn't currently handle multi-monitor systems explicitly, since those calculations
  584.     // require API functions that don't exist in Win95/NT (and thus would have to be loaded
  585.     // dynamically to allow the program to launch).  Therefore, windows will likely wind up
  586.     // being centered across the total dimensions of all monitors, which usually results in
  587.     // half being on one monitor and half in the other.  This doesn't seem too terrible and
  588.     // might even be what the user wants in some cases (i.e. for really big windows).
  589.     // See comments above for why work_rect.left and top are added in (they aren't always zero).
  590.     if (xpos == COORD_UNSPECIFIED)
  591.         xpos = work_rect.left + ((work_width - main_width) / 2);  // Don't use splash.width.
  592.     if (ypos == COORD_UNSPECIFIED)
  593.         ypos = work_rect.top + ((work_height - main_height) / 2);  // Don't use splash.width.
  594.  
  595.     // CREATE Main Splash Window
  596.     // It seems best to make this an unowned window for two reasons:
  597.     // 1) It will get its own task bar icon then, which is usually desirable for cases where
  598.     //    there are several progress/splash windows or the window is monitoring something.
  599.     // 2) The progress/splash window won't prevent the main window from being used (owned windows
  600.     //    prevent their owners from ever becoming active).
  601.     // However, it seems likely that some users would want the above to be configurable,
  602.     // so now there is an option to change this behavior.
  603.     HWND dialog_owner = THREAD_DIALOG_OWNER;  // Resolve macro only once to reduce code size.
  604.     if (!(splash.hwnd = CreateWindowEx(exstyle, WINDOW_CLASS_SPLASH, aTitle, style, xpos, ypos
  605.         , main_width, main_height, owned ? (dialog_owner ? dialog_owner : g_hWnd) : NULL // v1.0.35.01: For flexibility, allow these windows to be owned by GUIs via +OwnDialogs.
  606.         , NULL, g_hInstance, NULL)))
  607.         return FAIL;  // No error msg since so rare.
  608.  
  609.     if ((style & WS_SYSMENU) || !owned)
  610.     {
  611.         // Setting the small icon puts it in the upper left corner of the dialog window.
  612.         // Setting the big icon makes the dialog show up correctly in the Alt-Tab menu (but big seems to
  613.         // have no effect unless the window is unowned, i.e. it has a button on the task bar).
  614.         LPARAM main_icon = (LPARAM)(g_script.mCustomIcon ? g_script.mCustomIcon
  615.             : LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_MAIN), IMAGE_ICON, 0, 0, LR_SHARED)); // Use LR_SHARED to conserve memory (since the main icon is loaded for so many purposes).
  616.         if (style & WS_SYSMENU)
  617.             SendMessage(splash.hwnd, WM_SETICON, ICON_SMALL, main_icon);
  618.         if (!owned)
  619.             SendMessage(splash.hwnd, WM_SETICON, ICON_BIG, main_icon);
  620.     }
  621.  
  622.     // Update client rect in case it was resized due to being too large (above) or in case the OS
  623.     // auto-resized it for some reason.  These updated values are also used by SPLASH_CALC_CTRL_WIDTH
  624.     // to position the static text controls so that text will be centered properly:
  625.     GetClientRect(splash.hwnd, &client_rect);
  626.     splash.height = client_rect.bottom;
  627.     splash.width = client_rect.right;
  628.     int control_width = client_rect.right - (splash.margin_x * 2);
  629.  
  630.     // CREATE Main label
  631.     if (*aMainText)
  632.     {
  633.         splash.hwnd_text1 = CreateWindowEx(0, "static", aMainText
  634.             , WS_CHILD|WS_VISIBLE|SS_NOPREFIX|(centered_main ? SS_CENTER : SS_LEFT)
  635.             , PROGRESS_MAIN_POS, splash.hwnd, NULL, g_hInstance, NULL);
  636.         SendMessage(splash.hwnd_text1, WM_SETFONT, (WPARAM)(splash.hfont1 ? splash.hfont1 : hfont_default), MAKELPARAM(TRUE, 0));
  637.     }
  638.  
  639.     if (!aSplashImage && splash.object_height > 0) // Progress window
  640.     {
  641.         // CREATE Progress control (always starts off at its default position as determined by OS/common controls):
  642.         if (splash.hwnd_bar = CreateWindowEx(WS_EX_CLIENTEDGE, PROGRESS_CLASS, NULL, WS_CHILD|WS_VISIBLE|PBS_SMOOTH
  643.             , PROGRESS_BAR_POS, splash.hwnd, NULL, NULL, NULL))
  644.         {
  645.             if (range_min || range_max) // i.e. if both are zero, leave it at the default range, which is 0-100.
  646.             {
  647.                 if (range_min > -1 && range_min < 0x10000 && range_max > -1 && range_max < 0x10000)
  648.                     // Since the values fall within the bounds for Win95/NT to support, use the old method
  649.                     // in case Win95/NT lacks MSIE 3.0:
  650.                     SendMessage(splash.hwnd_bar, PBM_SETRANGE, 0, MAKELPARAM(range_min, range_max));
  651.                 else
  652.                     SendMessage(splash.hwnd_bar, PBM_SETRANGE32, range_min, range_max);
  653.             }
  654.  
  655.  
  656.             if (bar_color != CLR_DEFAULT)
  657.             {
  658.                 // Remove visual styles so that specified color will be obeyed:
  659.                 MySetWindowTheme(splash.hwnd_bar, L"", L"");
  660.                 SendMessage(splash.hwnd_bar, PBM_SETBARCOLOR, 0, bar_color); // Set color.
  661.             }
  662.             if (splash.color_bk != CLR_DEFAULT)
  663.                 SendMessage(splash.hwnd_bar, PBM_SETBKCOLOR, 0, splash.color_bk); // Set color.
  664.             if (bar_pos_has_been_set) // Note that the window is not yet visible at this stage.
  665.                 // This happens when the window doesn't exist and a command such as the following is given:
  666.                 // Progress, 50 [, ...].  As of v1.0.25, it also happens via the new 'P' option letter:
  667.                 SendMessage(splash.hwnd_bar, PBM_SETPOS, (WPARAM)bar_pos, 0);
  668.             else // Ask the control its starting/default position in case a custom range is in effect.
  669.                 bar_pos = (int)SendMessage(splash.hwnd_bar, PBM_GETPOS, 0, 0);
  670.             splash.bar_pos = bar_pos; // Save the current position to avoid redraws when future positions are identical to current.
  671.         }
  672.     }
  673.  
  674.     // CREATE Sub label
  675.     if (splash.hwnd_text2 = CreateWindowEx(0, "static", aSubText
  676.         , WS_CHILD|WS_VISIBLE|SS_NOPREFIX|(centered_sub ? SS_CENTER : SS_LEFT)
  677.         , PROGRESS_SUB_POS, splash.hwnd, NULL, g_hInstance, NULL))
  678.         SendMessage(splash.hwnd_text2, WM_SETFONT, (WPARAM)(splash.hfont2 ? splash.hfont2 : hfont_default), MAKELPARAM(TRUE, 0));
  679.  
  680.     // Show it without activating it.  Even with options that allow the window to be activated (such
  681.     // as movable), it seems best to do this to prevent changing the current foreground window, which
  682.     // is usually desirable for progress/splash windows since they should be seen but not be disruptive:
  683.     if (!initially_hidden)
  684.         ShowWindow(splash.hwnd,  SW_SHOWNOACTIVATE);
  685.     return OK;
  686. }
  687.  
  688.  
  689.  
  690. ResultType Line::ToolTip(char *aText, char *aX, char *aY, char *aID)
  691. {
  692.     int window_index = *aID ? ATOI(aID) - 1 : 0;
  693.     if (window_index < 0 || window_index >= MAX_TOOLTIPS)
  694.         return LineError("Max window number is " MAX_TOOLTIPS_STR "." ERR_ABORT, FAIL, aID);
  695.     HWND tip_hwnd = g_hWndToolTip[window_index];
  696.  
  697.     // Destroy windows except the first (for performance) so that resources/mem are conserved.
  698.     // The first window will be hidden by the TTM_UPDATETIPTEXT message if aText is blank.
  699.     // UPDATE: For simplicity, destroy even the first in this way, because otherwise a script
  700.     // that turns off a non-existent first tooltip window then later turns it on will cause
  701.     // the window to appear in an incorrect position.  Example:
  702.     // ToolTip
  703.     // ToolTip, text, 388, 24
  704.     // Sleep, 1000
  705.     // ToolTip, text, 388, 24
  706.     if (!*aText)
  707.     {
  708.         if (tip_hwnd && IsWindow(tip_hwnd))
  709.             DestroyWindow(tip_hwnd);
  710.         g_hWndToolTip[window_index] = NULL;
  711.         return OK;
  712.     }
  713.  
  714.     // Use virtual desktop so that tooltip can move onto non-primary monitor in a multi-monitor system:
  715.     RECT dtw;
  716.     GetVirtualDesktopRect(dtw);
  717.  
  718.     bool one_or_both_coords_unspecified = !*aX || !*aY;
  719.     POINT pt, pt_cursor;
  720.     if (one_or_both_coords_unspecified)
  721.     {
  722.         // Don't call GetCursorPos() unless absolutely needed because it seems to mess
  723.         // up double-click timing, at least on XP.  UPDATE: Is isn't GetCursorPos() that's
  724.         // interfering with double clicks, so it seems it must be the displaying of the ToolTip
  725.         // window itself.
  726.         GetCursorPos(&pt_cursor);
  727.         pt.x = pt_cursor.x + 16;  // Set default spot to be near the mouse cursor.
  728.         pt.y = pt_cursor.y + 16;  // Use 16 to prevent the tooltip from overlapping large cursors.
  729.         // Update: Below is no longer needed due to a better fix further down that handles multi-line tooltips.
  730.         // 20 seems to be about the right amount to prevent it from "warping" to the top of the screen,
  731.         // at least on XP:
  732.         //if (pt.y > dtw.bottom - 20)
  733.         //    pt.y = dtw.bottom - 20;
  734.     }
  735.  
  736.     RECT rect = {0};
  737.     if ((*aX || *aY) && !(g.CoordMode & COORD_MODE_TOOLTIP)) // Need the rect.
  738.     {
  739.         if (!GetWindowRect(GetForegroundWindow(), &rect))
  740.             return OK;  // Don't bother setting ErrorLevel with this command.
  741.     }
  742.     //else leave all of rect's members initialized to zero.
  743.  
  744.     // This will also convert from relative to screen coordinates if rect contains non-zero values:
  745.     if (*aX)
  746.         pt.x = ATOI(aX) + rect.left;
  747.     if (*aY)
  748.         pt.y = ATOI(aY) + rect.top;
  749.  
  750.     TOOLINFO ti = {0};
  751.     ti.cbSize = sizeof(ti) - sizeof(void *); // Fixed for v1.0.36.05: Tooltips fail to work on Win9x and probably NT4/2000 unless the size for the *lpReserved member in _WIN32_WINNT 0x0501 is omitted.
  752.     ti.uFlags = TTF_TRACK;
  753.     ti.lpszText = aText;
  754.     // Note that the ToolTip won't work if ti.hwnd is assigned the HWND from GetDesktopWindow().
  755.     // All of ti's other members are left at NULL/0, including the following:
  756.     //ti.hinst = NULL;
  757.     //ti.uId = 0;
  758.     //ti.rect.left = ti.rect.top = ti.rect.right = ti.rect.bottom = 0;
  759.  
  760.     // My: This does more harm that good (it causes the cursor to warp from the right side to the left
  761.     // if it gets to close to the right side), so for now, I did a different fix (above) instead:
  762.     //ti.rect.bottom = dtw.bottom;
  763.     //ti.rect.right = dtw.right;
  764.     //ti.rect.top = dtw.top;
  765.     //ti.rect.left = dtw.left;
  766.  
  767.     // No need to use SendMessageTimeout() since the ToolTip() is owned by our own thread, which
  768.     // (since we're here) we know is not hung or heavily occupied.
  769.  
  770.     // v1.0.40.12: Added the IsWindow() check below to recreate the tooltip in cases where it was destroyed
  771.     // by external means such as Alt-F4 or WinClose.
  772.     if (!tip_hwnd || !IsWindow(tip_hwnd))
  773.     {
  774.         // This this window has no owner, it won't be automatically destroyed when its owner is.
  775.         // Thus, it will be explicitly by the program's exit function.
  776.         tip_hwnd = g_hWndToolTip[window_index] = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, TTS_NOPREFIX | TTS_ALWAYSTIP
  777.             , CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL);
  778.         SendMessage(tip_hwnd, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti);
  779.         // v1.0.21: GetSystemMetrics(SM_CXSCREEN) is used for the maximum width because even on a
  780.         // multi-monitor system, most users would not want a tip window to stretch across multiple monitors:
  781.         SendMessage(tip_hwnd, TTM_SETMAXTIPWIDTH, 0, (LPARAM)GetSystemMetrics(SM_CXSCREEN));
  782.         // Must do these next two when the window is first created, otherwise GetWindowRect() below will retrieve
  783.         // a tooltip window size that is quite a bit taller than it winds up being:
  784.         SendMessage(tip_hwnd, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x, pt.y));
  785.         SendMessage(tip_hwnd, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
  786.     }
  787.     // Bugfix for v1.0.21: The below is now called unconditionally, even if the above newly created the window.
  788.     // If this is not done, the tip window will fail to appear the first time it is invoked, at least when
  789.     // all of the following are true:
  790.     // 1) Windows XP;
  791.     // 2) Common controls v6 (via manifest);
  792.     // 3) "Control Panel >> Display >> Effects >> Use transition >> Fade effect" setting is in effect.
  793.     SendMessage(tip_hwnd, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
  794.  
  795.     RECT ttw = {0};
  796.     GetWindowRect(tip_hwnd, &ttw); // Must be called this late to ensure the tooltip has been created by above.
  797.     int tt_width = ttw.right - ttw.left;
  798.     int tt_height = ttw.bottom - ttw.top;
  799.  
  800.     // v1.0.21: Revised for multi-monitor support.  I read somewhere that dtw.left can be negative (perhaps
  801.     // if the secondary monitor is to the left of the primary).  So it seems best to assume it is possible:
  802.     if (pt.x + tt_width >= dtw.right)
  803.         pt.x = dtw.right - tt_width - 1;
  804.     if (pt.y + tt_height >= dtw.bottom)
  805.         pt.y = dtw.bottom - tt_height - 1;
  806.     // It seems best not to have each of the below paired with the above.  This is because it allows
  807.     // the flexibility to explicitly move the tooltip above or to the left of the screen.  Such a feat
  808.     // should only be possible if done via explicitly passed-in negative coordinates for aX and/or aY.
  809.     // In other words, it should be impossible for a tooltip window to follow the mouse cursor somewhere
  810.     // off the virtual screen because:
  811.     // 1) The mouse cursor is normally trapped within the bounds of the virtual screen.
  812.     // 2) The tooltip window defaults to appearing South-East of the cursor.  It can only appear
  813.     //    in some other quadrant if jammed against the right or bottom edges of the screen, in which
  814.     //    case it can't be partially above or to the left of the virtual screen unless it's really
  815.     //    huge, which seems very unlikely given that it's limited to the maximum width of the
  816.     //    primary display as set by TTM_SETMAXTIPWIDTH above.
  817.     //else if (pt.x < dtw.left) // Should be impossible for this to happen due to mouse being off the screen.
  818.     //    pt.x = dtw.left;      // But could happen if user explicitly passed in a coord that was too negative.
  819.     //...
  820.     //else if (pt.y < dtw.top)
  821.     //    pt.y = dtw.top;
  822.  
  823.     if (one_or_both_coords_unspecified)
  824.     {
  825.         // Since Tooltip is being shown at the cursor's coordinates, try to ensure that the above
  826.         // adjustment doesn't result in the cursor being inside the tooltip's window boundaries,
  827.         // since that tends to cause problems such as blocking the tray area (which can make a
  828.         // tootip script impossible to terminate).  Normally, that can only happen in this case
  829.         // (one_or_both_coords_unspecified == true) when the cursor is near the buttom-right
  830.         // corner of the screen (unless the mouse is moving more quickly than the script's
  831.         // ToolTip update-frequency can cope with, but that seems inconsequential since it
  832.         // will adjust when the cursor slows down):
  833.         ttw.left = pt.x;
  834.         ttw.top = pt.y;
  835.         ttw.right = ttw.left + tt_width;
  836.         ttw.bottom = ttw.top + tt_height;
  837.         if (pt_cursor.x >= ttw.left && pt_cursor.x <= ttw.right && pt_cursor.y >= ttw.top && pt_cursor.y <= ttw.bottom)
  838.         {
  839.             // Push the tool tip to the upper-left side, since normally the only way the cursor can
  840.             // be inside its boundaries (when one_or_both_coords_unspecified == true) is when the
  841.             // cursor is near the bottom right corner of the screen.
  842.             pt.x = pt_cursor.x - tt_width - 3;    // Use a small offset since it can't overlap the cursor
  843.             pt.y = pt_cursor.y - tt_height - 3;   // when pushed to the the upper-left side of it.
  844.         }
  845.     }
  846.  
  847.     SendMessage(tip_hwnd, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x, pt.y));
  848.     // And do a TTM_TRACKACTIVATE even if the tooltip window already existed upon entry to this function,
  849.     // so that in case it was hidden or dismissed while its HWND still exists, it will be shown again:
  850.     SendMessage(tip_hwnd, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
  851.     return OK;
  852. }
  853.  
  854.  
  855.  
  856. ResultType Line::TrayTip(char *aTitle, char *aText, char *aTimeout, char *aOptions)
  857. {
  858.     if (!g_os.IsWin2000orLater()) // Older OSes do not support it, so do nothing.
  859.         return OK;
  860.     NOTIFYICONDATA nic = {0};
  861.     nic.cbSize = sizeof(nic);
  862.     nic.uID = AHK_NOTIFYICON;  // This must match our tray icon's uID or Shell_NotifyIcon() will return failure.
  863.     nic.hWnd = g_hWnd;
  864.     nic.uFlags = NIF_INFO;
  865.     nic.uTimeout = ATOI(aTimeout) * 1000;
  866.     nic.dwInfoFlags = ATOI(aOptions);
  867.     strlcpy(nic.szInfoTitle, aTitle, sizeof(nic.szInfoTitle)); // Empty title omits the title line entirely.
  868.     strlcpy(nic.szInfo, aText, sizeof(nic.szInfo));    // Empty text removes the balloon.
  869.     Shell_NotifyIcon(NIM_MODIFY, &nic);
  870.     return OK; // i.e. never a critical error if it fails.
  871. }
  872.  
  873.  
  874.  
  875. ResultType Line::Transform(char *aCmd, char *aValue1, char *aValue2)
  876. {
  877.     Var &output_var = *OUTPUT_VAR;
  878.     TransformCmds trans_cmd = ConvertTransformCmd(aCmd);
  879.     // Since command names are validated at load-time, this only happens if the command name
  880.     // was contained in a variable reference.  Since that is very rare, output_var is simply
  881.     // made blank to indicate the problem:
  882.     if (trans_cmd == TRANS_CMD_INVALID)
  883.         return output_var.Assign();
  884.  
  885.     char buf[32];
  886.     int value32;
  887.     INT64 value64;
  888.     double value_double1, value_double2, multiplier;
  889.     double result_double;
  890.     SymbolType value1_is_pure_numeric, value2_is_pure_numeric;
  891.  
  892.     #undef DETERMINE_NUMERIC_TYPES
  893.     #define DETERMINE_NUMERIC_TYPES \
  894.         value1_is_pure_numeric = IsPureNumeric(aValue1, true, false, true);\
  895.         value2_is_pure_numeric = IsPureNumeric(aValue2, true, false, true);
  896.  
  897.     #define EITHER_IS_FLOAT (value1_is_pure_numeric == PURE_FLOAT || value2_is_pure_numeric == PURE_FLOAT)
  898.  
  899.     // If neither input is float, the result is assigned as an integer (i.e. no decimal places):
  900.     #define ASSIGN_BASED_ON_TYPE \
  901.         DETERMINE_NUMERIC_TYPES \
  902.         if (EITHER_IS_FLOAT) \
  903.             return output_var.Assign(result_double);\
  904.         else\
  905.             return output_var.Assign((INT64)result_double);
  906.  
  907.     // Have a negative exponent always cause a floating point result:
  908.     #define ASSIGN_BASED_ON_TYPE_POW \
  909.         DETERMINE_NUMERIC_TYPES \
  910.         if (EITHER_IS_FLOAT || value_double2 < 0) \
  911.             return output_var.Assign(result_double);\
  912.         else\
  913.             return output_var.Assign((INT64)result_double);
  914.  
  915.     #define ASSIGN_BASED_ON_TYPE_SINGLE \
  916.         if (IsPureNumeric(aValue1, true, false, true) == PURE_FLOAT)\
  917.             return output_var.Assign(result_double);\
  918.         else\
  919.             return output_var.Assign((INT64)result_double);
  920.  
  921.     // If rounding to an integer, ensure the result is stored as an integer:
  922.     #define ASSIGN_BASED_ON_TYPE_SINGLE_ROUND \
  923.         if (IsPureNumeric(aValue1, true, false, true) == PURE_FLOAT && value32 > 0)\
  924.             return output_var.Assign(result_double);\
  925.         else\
  926.             return output_var.Assign((INT64)result_double);
  927.  
  928.     switch(trans_cmd)
  929.     {
  930.     case TRANS_CMD_ASC:
  931.         if (*aValue1)
  932.             return output_var.Assign((int)(UCHAR)*aValue1); // Cast to UCHAR so that chars above Asc(128) show as positive.
  933.         else
  934.             return output_var.Assign();
  935.  
  936.     case TRANS_CMD_CHR:
  937.         value32 = ATOI(aValue1);
  938.         if (value32 < 0 || value32 > 255)
  939.             return output_var.Assign();
  940.         else
  941.         {
  942.             *buf = value32;  // Store ASCII value as a single-character string.
  943.             *(buf + 1) = '\0';
  944.             return output_var.Assign(buf);
  945.         }
  946.  
  947.     case TRANS_CMD_DEREF:
  948.         return Deref(&output_var, aValue1);
  949.  
  950.     case TRANS_CMD_UNICODE:
  951.         int char_count;
  952.         if (output_var.Type() == VAR_CLIPBOARD)
  953.         {
  954.             // Since the output var is the clipboard, the mode is autodetected as the following:
  955.             // Convert aValue1 from UTF-8 to Unicode and put the result onto the clipboard.
  956.             // MSDN: "Windows 95: Under the Microsoft Layer for Unicode, MultiByteToWideChar also
  957.             // supports CP_UTF7 and CP_UTF8."
  958.             if (   !(char_count = UTF8ToWideChar(aValue1, NULL, 0))   ) // Get required buffer size in WCHARs (includes terminator).
  959.                 return output_var.Assign(); // Make output_var (the clipboard in this case) blank to indicate failure.
  960.             LPVOID clip_buf;
  961.             if (   !(clip_buf = g_clip.PrepareForWrite(char_count * sizeof(WCHAR)))   )
  962.                 return output_var.Assign(); // Make output_var (the clipboard in this case) blank to indicate failure.
  963.             // Perform the conversion:
  964.             if (!UTF8ToWideChar(aValue1, (LPWSTR)clip_buf, char_count))
  965.             {
  966.                 g_clip.AbortWrite();
  967.                 return output_var.Assign(); // Make clipboard blank to indicate failure.
  968.             }
  969.             return g_clip.Commit(CF_UNICODETEXT); // Save as type Unicode. It will display any error that occurs.
  970.         }
  971.         // Otherwise, going in the reverse direction: convert the clipboard contents to UTF-8 and put
  972.         // the result into a normal variable.
  973.         if (!IsClipboardFormatAvailable(CF_UNICODETEXT) || !g_clip.Open()) // Relies on short-circuit boolean order.
  974.             return output_var.Assign(); // Make the (non-clipboard) output_var blank to indicate failure.
  975.         if (   !(g_clip.mClipMemNow = g_clip.GetClipboardDataTimeout(CF_UNICODETEXT)) // Relies on short-circuit boolean order.
  976.             || !(g_clip.mClipMemNowLocked = (char *)GlobalLock(g_clip.mClipMemNow))
  977.             || !(char_count = WideCharToUTF8((LPCWSTR)g_clip.mClipMemNowLocked, NULL, 0))   ) // char_count includes terminator.
  978.         {
  979.             // Above finds out how large the contents will be when converted to UTF-8.
  980.             // In this case, it failed to determine the count, perhaps due to Win95 lacking Unicode layer, etc.
  981.             g_clip.Close();
  982.             return output_var.Assign(); // Make the (non-clipboard) output_var blank to indicate failure.
  983.         }
  984.         // Othewise, it found the count.  Set up the output variable, enlarging it if needed:
  985.         if (output_var.Assign(NULL, char_count - 1) != OK) // Don't combine this with the above or below it can return FAIL.
  986.         {
  987.             g_clip.Close();
  988.             return FAIL;  // It already displayed the error.
  989.         }
  990.         // Perform the conversion:
  991.         char_count = WideCharToUTF8((LPCWSTR)g_clip.mClipMemNowLocked, output_var.Contents(), char_count);
  992.         g_clip.Close(); // Close the clipboard and free the memory.
  993.         output_var.Close(); // NOTE: Length() was already set properly by Assign() above. In case it's the clipboard, though currently it can't be since that would auto-detect as the reverse direction.
  994.         if (!char_count)
  995.             return output_var.Assign(); // Make non-clipboard output_var blank to indicate failure.
  996.         return OK;
  997.  
  998.     case TRANS_CMD_HTML:
  999.     {
  1000.         // These are the encoding-neutral translations for ASC 128 through 255 as shown by Dreamweaver.
  1001.         // It's possible that using just the &#number convention (e.g. € through ÿ) would be
  1002.         // more appropriate for some users, but that mode can be added in the future if it is ever
  1003.         // needed (by passing a mode setting for aValue2):
  1004.         // ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐
  1005.         // └┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ 
  1006.         static const char *sHtml[128] = { // v1.0.40.02: Removed leading '&' and trailing ';' to reduce code size.
  1007.               "euro", "#129", "sbquo", "fnof", "bdquo", "hellip", "dagger", "Dagger"
  1008.             , "circ", "permil", "Scaron", "lsaquo", "OElig", "#141", "#381", "#143"
  1009.             , "#144", "lsquo", "rsquo", "ldquo", "rdquo", "bull", "ndash", "mdash"
  1010.             , "tilde", "trade", "scaron", "rsaquo", "oelig", "#157", "#382", "Yuml"
  1011.             , "nbsp", "iexcl", "cent", "pound", "curren", "yen", "brvbar", "sect"
  1012.             , "uml", "copy", "ordf", "laquo", "not", "shy", "reg", "macr"
  1013.             , "deg", "plusmn", "sup2", "sup3", "acute", "micro", "para", "middot"
  1014.             , "cedil", "sup1", "ordm", "raquo", "frac14", "frac12", "frac34", "iquest"
  1015.             , "Agrave", "Aacute", "Acirc", "Atilde", "Auml", "Aring", "AElig", "Ccedil"
  1016.             , "Egrave", "Eacute", "Ecirc", "Euml", "Igrave", "Iacute", "Icirc", "Iuml"
  1017.             , "ETH", "Ntilde", "Ograve", "Oacute", "Ocirc", "Otilde", "Ouml", "times"
  1018.             , "Oslash", "Ugrave", "Uacute", "Ucirc", "Uuml", "Yacute", "THORN", "szlig"
  1019.             , "agrave", "aacute", "acirc", "atilde", "auml", "aring", "aelig", "ccedil"
  1020.             , "egrave", "eacute", "ecirc", "euml", "igrave", "iacute", "icirc", "iuml"
  1021.             , "eth", "ntilde", "ograve", "oacute", "ocirc", "otilde", "ouml", "divide"
  1022.             , "oslash", "ugrave", "uacute", "ucirc", "uuml", "yacute", "thorn", "yuml"
  1023.         };
  1024.  
  1025.         // Determine how long the result string will be so that the output variable can be expanded
  1026.         // to handle it:
  1027.         VarSizeType length;
  1028.         UCHAR *ucp;
  1029.         for (length = 0, ucp = (UCHAR *)aValue1; *ucp; ++ucp)
  1030.         {
  1031.             switch(*ucp)
  1032.             {
  1033.             case '"':  // "
  1034.                 length += 6;
  1035.                 break;
  1036.             case '&': // &
  1037.             case '\n': // <br>\n
  1038.                 length += 5;
  1039.                 break; // v1.0.45: Added missing break.  This had caused incorrect lengths inside some variables, which led to problems in places that relied on the accuracy of the internal lengths.
  1040.             case '<': // <
  1041.             case '>': // >
  1042.                 length += 4;
  1043.                 break; // v1.0.45: Added missing break.
  1044.             default:
  1045.                 if (*ucp > 127)
  1046.                     length += (VarSizeType)strlen(sHtml[*ucp - 128]) + 2; // +2 for the leading '&' and the trailing ';'.
  1047.                 else
  1048.                     ++length;
  1049.             }
  1050.         }
  1051.  
  1052.         // Set up the var, enlarging it if necessary.  If the output_var is of type VAR_CLIPBOARD,
  1053.         // this call will set up the clipboard for writing:
  1054.         if (output_var.Assign(NULL, length) != OK)
  1055.             return FAIL;  // It already displayed the error.
  1056.         char *contents = output_var.Contents();  // For performance and tracking.
  1057.  
  1058.         // Translate the text to HTML:
  1059.         for (ucp = (UCHAR *)aValue1; *ucp; ++ucp)
  1060.         {
  1061.             switch(*ucp)
  1062.             {
  1063.             case '"':  // "
  1064.                 strcpy(contents, """);
  1065.                 contents += 6;
  1066.                 break;
  1067.             case '&': // &
  1068.                 strcpy(contents, "&");
  1069.                 contents += 5;
  1070.                 break;
  1071.             case '\n': // <br>\n
  1072.                 strcpy(contents, "<br>\n");
  1073.                 contents += 5;
  1074.                 break;
  1075.             case '<': // <
  1076.                 strcpy(contents, "<");
  1077.                 contents += 4;
  1078.                 break;
  1079.             case '>': // >
  1080.                 strcpy(contents, ">");
  1081.                 contents += 4;
  1082.                 break;
  1083.             default:
  1084.                 if (*ucp > 127)
  1085.                 {
  1086.                     *contents++ = '&'; // v1.0.40.02
  1087.                     strcpy(contents, sHtml[*ucp - 128]);
  1088.                     contents += strlen(contents); // Added as a fix in v1.0.41 (broken in v1.0.40.02).
  1089.                     *contents++ = ';'; // v1.0.40.02
  1090.                 }
  1091.                 else
  1092.                     *contents++ = *ucp;
  1093.             }
  1094.         }
  1095.         *contents = '\0';  // Terminate the string.
  1096.         return output_var.Close();  // In case it's the clipboard.
  1097.     }
  1098.  
  1099.     case TRANS_CMD_MOD:
  1100.         if (   !(value_double2 = ATOF(aValue2))   ) // Divide by zero, set it to be blank to indicate the problem.
  1101.             return output_var.Assign();
  1102.         // Otherwise:
  1103.         result_double = qmathFmod(ATOF(aValue1), value_double2);
  1104.         ASSIGN_BASED_ON_TYPE
  1105.  
  1106.     case TRANS_CMD_POW:
  1107.     {
  1108.         // v1.0.44.11: With Laszlo's help, negative bases are now supported as long as the exponent is not fractional.
  1109.         // See SYM_POWER in script_expression.cpp for similar code and more comments.
  1110.         value_double1 = ATOF(aValue1);
  1111.         value_double2 = ATOF(aValue2);
  1112.         bool value1_was_negative = (value_double1 < 0);
  1113.         if (value_double1 == 0.0 && value_double2 < 0  // In essense, this is divide-by-zero.
  1114.             || value1_was_negative && qmathFmod(value_double2, 1.0) != 0.0) // Negative base but exponent isn't close enough to being an integer: unsupported (to simplify code).
  1115.             return output_var.Assign();  // Return a consistent result (blank) rather than something that varies.
  1116.         // Otherwise:
  1117.         if (value1_was_negative)
  1118.             value_double1 = -value_double1; // Force a positive due to the limitiations of qmathPow().
  1119.         result_double = qmathPow(value_double1, value_double2);
  1120.         if (value1_was_negative && qmathFabs(qmathFmod(value_double2, 2.0)) == 1.0) // Negative base and exactly-odd exponent (otherwise, it can only be zero or even because if not it would have returned higher above).
  1121.             result_double = -result_double;
  1122.         ASSIGN_BASED_ON_TYPE_POW
  1123.     }
  1124.  
  1125.     case TRANS_CMD_EXP:
  1126.         return output_var.Assign(qmathExp(ATOF(aValue1)));
  1127.  
  1128.     case TRANS_CMD_SQRT:
  1129.         value_double1 = ATOF(aValue1);
  1130.         if (value_double1 < 0)
  1131.             return output_var.Assign();
  1132.         return output_var.Assign(qmathSqrt(value_double1));
  1133.  
  1134.     case TRANS_CMD_LOG:
  1135.         value_double1 = ATOF(aValue1);
  1136.         if (value_double1 < 0)
  1137.             return output_var.Assign();
  1138.         return output_var.Assign(qmathLog10(ATOF(aValue1)));
  1139.  
  1140.     case TRANS_CMD_LN:
  1141.         value_double1 = ATOF(aValue1);
  1142.         if (value_double1 < 0)
  1143.             return output_var.Assign();
  1144.         return output_var.Assign(qmathLog(ATOF(aValue1)));
  1145.  
  1146.     case TRANS_CMD_ROUND:
  1147.         // In the future, a string conversion algorithm might be better to avoid the loss
  1148.         // of 64-bit integer precision that it currently caused by the use of doubles in
  1149.         // the calculation:
  1150.         value32 = ATOI(aValue2);
  1151.         multiplier = *aValue2 ? qmathPow(10, value32) : 1;
  1152.         value_double1 = ATOF(aValue1);
  1153.         result_double = (value_double1 >= 0.0 ? qmathFloor(value_double1 * multiplier + 0.5)
  1154.             : qmathCeil(value_double1 * multiplier - 0.5)) / multiplier;
  1155.         ASSIGN_BASED_ON_TYPE_SINGLE_ROUND
  1156.  
  1157.     case TRANS_CMD_CEIL:
  1158.     case TRANS_CMD_FLOOR:
  1159.         // The code here is similar to that in BIF_FloorCeil(), so maintain them together.
  1160.         result_double = ATOF(aValue1);
  1161.         result_double = (trans_cmd == TRANS_CMD_FLOOR) ? qmathFloor(result_double) : qmathCeil(result_double);
  1162.         return output_var.Assign((__int64)(result_double + (result_double > 0 ? 0.2 : -0.2))); // Fixed for v1.0.40.05: See comments in BIF_FloorCeil() for details.
  1163.  
  1164.     case TRANS_CMD_ABS:
  1165.     {
  1166.         // Seems better to convert as string to avoid loss of 64-bit integer precision
  1167.         // that would be caused by conversion to double.  I think this will work even
  1168.         // for negative hex numbers that are close to the 64-bit limit since they too have
  1169.         // a minus sign when generated by the script (e.g. -0x1).
  1170.         //result_double = qmathFabs(ATOF(aValue1));
  1171.         //ASSIGN_BASED_ON_TYPE_SINGLE
  1172.         char *cp = omit_leading_whitespace(aValue1); // i.e. caller doesn't have to have ltrimmed it.
  1173.         if (*cp == '-')
  1174.             return output_var.Assign(cp + 1);  // Omit the first minus sign (simple conversion only).
  1175.         // Otherwise, no minus sign, so just omit the leading whitespace for consistency:
  1176.         return output_var.Assign(cp);
  1177.     }
  1178.  
  1179.     case TRANS_CMD_SIN:
  1180.         return output_var.Assign(qmathSin(ATOF(aValue1)));
  1181.  
  1182.     case TRANS_CMD_COS:
  1183.         return output_var.Assign(qmathCos(ATOF(aValue1)));
  1184.  
  1185.     case TRANS_CMD_TAN:
  1186.         return output_var.Assign(qmathTan(ATOF(aValue1)));
  1187.  
  1188.     case TRANS_CMD_ASIN:
  1189.         value_double1 = ATOF(aValue1);
  1190.         if (value_double1 > 1 || value_double1 < -1)
  1191.             return output_var.Assign(); // ASin and ACos aren't defined for other values.
  1192.         return output_var.Assign(qmathAsin(ATOF(aValue1)));
  1193.  
  1194.     case TRANS_CMD_ACOS:
  1195.         value_double1 = ATOF(aValue1);
  1196.         if (value_double1 > 1 || value_double1 < -1)
  1197.             return output_var.Assign(); // ASin and ACos aren't defined for other values.
  1198.         return output_var.Assign(qmathAcos(ATOF(aValue1)));
  1199.  
  1200.     case TRANS_CMD_ATAN:
  1201.         return output_var.Assign(qmathAtan(ATOF(aValue1)));
  1202.  
  1203.     // For all of the below bitwise operations:
  1204.     // Seems better to convert to signed rather than unsigned so that signed values can
  1205.     // be supported.  i.e. it seems better to trade one bit in capacity in order to support
  1206.     // negative numbers.  Another reason is that commands such as IfEquals use ATOI64 (signed),
  1207.     // so if we were to produce unsigned 64 bit values here, they would be somewhat incompatible
  1208.     // with other script operations.
  1209.     case TRANS_CMD_BITAND:
  1210.         return output_var.Assign(ATOI64(aValue1) & ATOI64(aValue2));
  1211.  
  1212.     case TRANS_CMD_BITOR:
  1213.         return output_var.Assign(ATOI64(aValue1) | ATOI64(aValue2));
  1214.  
  1215.     case TRANS_CMD_BITXOR:
  1216.         return output_var.Assign(ATOI64(aValue1) ^ ATOI64(aValue2));
  1217.  
  1218.     case TRANS_CMD_BITNOT:
  1219.         value64 = ATOI64(aValue1);
  1220.         if (value64 < 0 || value64 > UINT_MAX)
  1221.             // Treat it as a 64-bit signed value, since no other aspects of the program
  1222.             // (e.g. IfEqual) will recognize an unsigned 64 bit number.
  1223.             return output_var.Assign(~value64);
  1224.         else
  1225.             // Treat it as a 32-bit unsigned value when inverting and assigning.  This is
  1226.             // because assigning it as a signed value would "convert" it into a 64-bit
  1227.             // value, which in turn is caused by the fact that the script sees all negative
  1228.             // numbers as 64-bit values (e.g. -1 is 0xFFFFFFFFFFFFFFFF).
  1229.             return output_var.Assign(~(DWORD)value64);
  1230.  
  1231.     case TRANS_CMD_BITSHIFTLEFT:  // Equivalent to multiplying by 2^value2
  1232.         return output_var.Assign(ATOI64(aValue1) << ATOI(aValue2));
  1233.  
  1234.     case TRANS_CMD_BITSHIFTRIGHT:  // Equivalent to dividing (integer) by 2^value2
  1235.         return output_var.Assign(ATOI64(aValue1) >> ATOI(aValue2));
  1236.     }
  1237.  
  1238.     return FAIL;  // Never executed (increases maintainability and avoids compiler warning).
  1239. }
  1240.  
  1241.  
  1242.  
  1243. ResultType Line::Input()
  1244. // OVERVIEW:
  1245. // Although a script can have many concurrent quasi-threads, there can only be one input
  1246. // at a time.  Thus, if an input is ongoing and a new thread starts, and it begins its
  1247. // own input, that input should terminate the prior input prior to beginning the new one.
  1248. // In a "worst case" scenario, each interrupted quasi-thread could have its own
  1249. // input, which is in turn terminated by the thread that interrupts it.  Every time
  1250. // this function returns, it must be sure to set g_input.status to INPUT_OFF beforehand.
  1251. // This signals the quasi-threads beneath, when they finally return, that their input
  1252. // was terminated due to a new input that took precedence.
  1253. {
  1254.     if (g_os.IsWin9x()) // v1.0.44.14: For simplicity, do nothing on Win9x rather than try to see if it actually supports the hook (such as if its some kind of emultated/hybrid OS).
  1255.         return OK; // Could also set ErrorLevel to "Timeout" and output_var to be blank, but the benefits to backward compatibility seemed too dubious.
  1256.  
  1257.     // Since other script threads can interrupt this command while it's running, it's important that
  1258.     // this command not refer to sArgDeref[] and sArgVar[] anytime after an interruption becomes possible.
  1259.     // This is because an interrupting thread usually changes the values to something inappropriate for this thread.
  1260.     Var *output_var = OUTPUT_VAR; // See comment above.
  1261.     if (!output_var)
  1262.     {
  1263.         // No output variable, which due to load-time validation means there are no other args either.
  1264.         // This means that the user is specifically canceling the prior input (if any).  Thus, our
  1265.         // ErrorLevel here is set to 1 or 0, but the prior input's ErrorLevel will be set to "NewInput"
  1266.         // when its quasi-thread is resumed:
  1267.         bool prior_input_is_being_terminated = (g_input.status == INPUT_IN_PROGRESS);
  1268.         g_input.status = INPUT_OFF;
  1269.         return g_ErrorLevel->Assign(prior_input_is_being_terminated ? ERRORLEVEL_NONE : ERRORLEVEL_ERROR);
  1270.         // Above: It's considered an "error" of sorts when there is no prior input to terminate.
  1271.     }
  1272.  
  1273.     // Below are done directly this way rather than passed in as args mainly to emphasize that
  1274.     // ArgLength() can safely be called in Line methods like this one (which is done further below).
  1275.     // It also may also slightly improve performance and reduce code size.
  1276.     char *aOptions = ARG2, *aEndKeys = ARG3, *aMatchList = ARG4;
  1277.     // The aEndKeys string must be modifiable (not constant), since for performance reasons,
  1278.     // it's allowed to be temporarily altered by this function.
  1279.  
  1280.     // Set default in case of early return (we want these to be in effect even if
  1281.     // FAIL is returned for our thread, since the underlying thread that had the
  1282.     // active input prior to us didn't fail and it it needs to know how its input
  1283.     // was terminated):
  1284.     g_input.status = INPUT_OFF;
  1285.  
  1286.     //////////////////////////////////////////////
  1287.     // Set up sparse arrays according to aEndKeys:
  1288.     //////////////////////////////////////////////
  1289.     UCHAR end_vk[VK_ARRAY_COUNT] = {0};  // A sparse array that indicates which VKs terminate the input.
  1290.     UCHAR end_sc[SC_ARRAY_COUNT] = {0};  // A sparse array that indicates which SCs terminate the input.
  1291.  
  1292.     vk_type vk;
  1293.     sc_type sc = 0;
  1294.     modLR_type modifiersLR;
  1295.     size_t key_text_length;
  1296.     char *end_pos, single_char_string[2];
  1297.     single_char_string[1] = '\0'; // Init its second character once, since the loop only changes the first char.
  1298.  
  1299.     for (; *aEndKeys; ++aEndKeys) // This a modified version of the processing loop used in SendKeys().
  1300.     {
  1301.         vk = 0; // Set default.  Not strictly necessary but more maintainable.
  1302.         *single_char_string = '\0';  // Set default as "this key name is not a single-char string".
  1303.  
  1304.         switch (*aEndKeys)
  1305.         {
  1306.         case '}': continue;  // Important that these be ignored.
  1307.         case '{':
  1308.         {
  1309.             if (   !(end_pos = strchr(aEndKeys + 1, '}'))   )
  1310.                 continue;  // Do nothing, just ignore the unclosed '{' and continue.
  1311.             if (   !(key_text_length = end_pos - aEndKeys - 1)   )
  1312.             {
  1313.                 if (end_pos[1] == '}') // The string "{}}" has been encountered, which is interpreted as a single "}".
  1314.                 {
  1315.                     ++end_pos;
  1316.                     key_text_length = 1;
  1317.                 }
  1318.                 else // Empty braces {} were encountered.
  1319.                     continue;  // do nothing: let it proceed to the }, which will then be ignored.
  1320.             }
  1321.  
  1322.             *end_pos = '\0';  // temporarily terminate the string here.
  1323.  
  1324.             // v1.0.45: Fixed this section to differentiate between } and ] (and also { and [, as well as
  1325.             // anything else enclosed in {} that requirds END_KEY_WITH_SHIFT/END_KEY_WITHOUT_SHIFT consideration.
  1326.             modifiersLR = 0;  // Init prior to below.
  1327.             if (vk = TextToVK(aEndKeys + 1, &modifiersLR, true))
  1328.             {
  1329.                 if (key_text_length == 1)
  1330.                     *single_char_string = aEndKeys[1];
  1331.                 //else leave it at its default of "not a single-char key-name".
  1332.             }
  1333.             else // No virtual key, so try to find a scan code.
  1334.                 if (sc = TextToSC(aEndKeys + 1))
  1335.                     end_sc[sc] = END_KEY_ENABLED;
  1336.  
  1337.             *end_pos = '}';  // undo the temporary termination
  1338.  
  1339.             aEndKeys = end_pos;  // In prep for aEndKeys++ at the bottom of the loop.
  1340.             break; // Break out of the switch() and do the vk handling beneath it (if there is a vk).
  1341.         }
  1342.  
  1343.         default:
  1344.             *single_char_string = *aEndKeys;
  1345.             modifiersLR = 0;  // Init prior to below.
  1346.             vk = TextToVK(single_char_string, &modifiersLR, true);
  1347.         } // switch()
  1348.  
  1349.         if (vk) // A valid virtual key code was discovered above.
  1350.         {
  1351.             end_vk[vk] |= END_KEY_ENABLED; // Use of |= is essential for cases such as ";:".
  1352.             // Insist the shift key be down to form genuinely different symbols --
  1353.             // namely punctuation marks -- but not for alphabetic chars.  In the
  1354.             // future, an option can be added to the Options param to treat
  1355.             // end chars as case sensitive (if there is any demand for that):
  1356.             if (*single_char_string && !IsCharAlpha(*single_char_string)) // v1.0.46.05: Added check for "*single_char_string" so that non-single-char strings like {F9} work as end keys even when the Shift key is being held down (this fixes the behavior to be like it was in pre-v1.0.45).
  1357.             {
  1358.                 // Now we know it's not alphabetic, and it's not a key whose name
  1359.                 // is longer than one char such as a function key or numpad number.
  1360.                 // That leaves mostly just the number keys (top row) and all
  1361.                 // punctuation chars, which are the ones that we want to be
  1362.                 // distinguished between shifted and unshifted:
  1363.                 if (modifiersLR & (MOD_LSHIFT | MOD_RSHIFT))
  1364.                     end_vk[vk] |= END_KEY_WITH_SHIFT;
  1365.                 else
  1366.                     end_vk[vk] |= END_KEY_WITHOUT_SHIFT;
  1367.             }
  1368.         }
  1369.     } // for()
  1370.  
  1371.     /////////////////////////////////////////////////
  1372.     // Parse aMatchList into an array of key phrases:
  1373.     /////////////////////////////////////////////////
  1374.     char **realloc_temp;  // Needed since realloc returns NULL on failure but leaves original block allocated.
  1375.     g_input.MatchCount = 0;  // Set default.
  1376.     if (*aMatchList)
  1377.     {
  1378.         // If needed, create the array of pointers that points into MatchBuf to each match phrase:
  1379.         if (!g_input.match)
  1380.         {
  1381.             if (   !(g_input.match = (char **)malloc(INPUT_ARRAY_BLOCK_SIZE * sizeof(char *)))   )
  1382.                 return LineError(ERR_OUTOFMEM);  // Short msg. since so rare.
  1383.             g_input.MatchCountMax = INPUT_ARRAY_BLOCK_SIZE;
  1384.         }
  1385.         // If needed, create or enlarge the buffer that contains all the match phrases:
  1386.         size_t aMatchList_length = ArgLength(4); // Performs better than strlen(aMatchList);
  1387.         size_t space_needed = aMatchList_length + 1;  // +1 for the final zero terminator.
  1388.         if (space_needed > g_input.MatchBufSize)
  1389.         {
  1390.             g_input.MatchBufSize = (UINT)(space_needed > 4096 ? space_needed : 4096);
  1391.             if (g_input.MatchBuf) // free the old one since it's too small.
  1392.                 free(g_input.MatchBuf);
  1393.             if (   !(g_input.MatchBuf = (char *)malloc(g_input.MatchBufSize))   )
  1394.             {
  1395.                 g_input.MatchBufSize = 0;
  1396.                 return LineError(ERR_OUTOFMEM);  // Short msg. since so rare.
  1397.             }
  1398.         }
  1399.         // Copy aMatchList into the match buffer:
  1400.         char *source, *dest;
  1401.         for (source = aMatchList, dest = g_input.match[g_input.MatchCount] = g_input.MatchBuf
  1402.             ; *source; ++source)
  1403.         {
  1404.             if (*source != ',') // Not a comma, so just copy it over.
  1405.             {
  1406.                 *dest++ = *source;
  1407.                 continue;
  1408.             }
  1409.             // Otherwise: it's a comma, which becomes the terminator of the previous key phrase unless
  1410.             // it's a double comma, in which case it's considered to be part of the previous phrase
  1411.             // rather than the next.
  1412.             if (*(source + 1) == ',') // double comma
  1413.             {
  1414.                 *dest++ = *source;
  1415.                 ++source;  // Omit the second comma of the pair, i.e. each pair becomes a single literal comma.
  1416.                 continue;
  1417.             }
  1418.             // Otherwise, this is a delimiting comma.
  1419.             *dest = '\0';
  1420.             // If the previous item is blank -- which I think can only happen now if the MatchList
  1421.             // begins with an orphaned comma (since two adjacent commas resolve to one literal comma)
  1422.             // -- don't add it to the match list:
  1423.             if (*g_input.match[g_input.MatchCount])
  1424.             {
  1425.                 ++g_input.MatchCount;
  1426.                 g_input.match[g_input.MatchCount] = ++dest;
  1427.                 *dest = '\0';  // Init to prevent crash on ophaned comma such as "btw,otoh,"
  1428.             }
  1429.             if (*(source + 1)) // There is a next element.
  1430.             {
  1431.                 if (g_input.MatchCount >= g_input.MatchCountMax) // Rarely needed, so just realloc() to expand.
  1432.                 {
  1433.                     // Expand the array by one block:
  1434.                     if (   !(realloc_temp = (char **)realloc(g_input.match  // Must use a temp variable.
  1435.                         , (g_input.MatchCountMax + INPUT_ARRAY_BLOCK_SIZE) * sizeof(char *)))   )
  1436.                         return LineError(ERR_OUTOFMEM);  // Short msg. since so rare.
  1437.                     g_input.match = realloc_temp;
  1438.                     g_input.MatchCountMax += INPUT_ARRAY_BLOCK_SIZE;
  1439.                 }
  1440.             }
  1441.         } // for()
  1442.         *dest = '\0';  // Terminate the last item.
  1443.         // This check is necessary for only a single isolated case: When the match list
  1444.         // consists of nothing except a single comma.  See above comment for details:
  1445.         if (*g_input.match[g_input.MatchCount]) // i.e. omit empty strings from the match list.
  1446.             ++g_input.MatchCount;
  1447.     }
  1448.  
  1449.     // Notes about the below macro:
  1450.     // In case the Input timer has already put a WM_TIMER msg in our queue before we killed it,
  1451.     // clean out the queue now to avoid any chance that such a WM_TIMER message will take effect
  1452.     // later when it would be unexpected and might interfere with this input.  To avoid an
  1453.     // unnecessary call to PeekMessage(), which has been known to yield our timeslice to other
  1454.     // processes if the CPU is under load (which might be undesirable if this input is
  1455.     // time-critical, such as in a game), call GetQueueStatus() to see if there are any timer
  1456.     // messages in the queue.  I believe that GetQueueStatus(), unlike PeekMessage(), does not
  1457.     // have the nasty/undocumented side-effect of yielding our timeslice under certain hard-to-reproduce
  1458.     // circumstances, but Google and MSDN are completely devoid of any confirming info on this.
  1459.     #define KILL_AND_PURGE_INPUT_TIMER \
  1460.     if (g_InputTimerExists)\
  1461.     {\
  1462.         KILL_INPUT_TIMER \
  1463.         if (HIWORD(GetQueueStatus(QS_TIMER)) & QS_TIMER)\
  1464.             MsgSleep(-1);\
  1465.     }
  1466.  
  1467.     // Be sure to get rid of the timer if it exists due to a prior, ongoing input.
  1468.     // It seems best to do this only after signaling the hook to start the input
  1469.     // so that it's MsgSleep(-1), if it launches a new hotkey or timed subroutine,
  1470.     // will be less likely to interrupt us during our setup of the input, i.e.
  1471.     // it seems best that we put the input in progress prior to allowing any
  1472.     // interruption.  UPDATE: Must do this before changing to INPUT_IN_PROGRESS
  1473.     // because otherwise the purging of the timer message might call InputTimeout(),
  1474.     // which in turn would set the status immediately to INPUT_TIMED_OUT:
  1475.     KILL_AND_PURGE_INPUT_TIMER
  1476.  
  1477.     //////////////////////////////////////////////////////////////
  1478.     // Initialize buffers and state variables for use by the hook:
  1479.     //////////////////////////////////////////////////////////////
  1480.     // Set the defaults that will be in effect unless overridden by an item in aOptions:
  1481.     g_input.BackspaceIsUndo = true;
  1482.     g_input.CaseSensitive = false;
  1483.     g_input.IgnoreAHKInput = false;
  1484.     g_input.TranscribeModifiedKeys = false;
  1485.     g_input.Visible = false;
  1486.     g_input.FindAnywhere = false;
  1487.     int timeout = 0;  // Set default.
  1488.     char input_buf[INPUT_BUFFER_SIZE] = ""; // Will contain the actual input from the user.
  1489.     g_input.buffer = input_buf;
  1490.     g_input.BufferLength = 0;
  1491.     g_input.BufferLengthMax = INPUT_BUFFER_SIZE - 1;
  1492.  
  1493.     for (char *cp = aOptions; *cp; ++cp)
  1494.     {
  1495.         switch(toupper(*cp))
  1496.         {
  1497.         case 'B':
  1498.             g_input.BackspaceIsUndo = false;
  1499.             break;
  1500.         case 'C':
  1501.             g_input.CaseSensitive = true;
  1502.             break;
  1503.         case 'I':
  1504.             g_input.IgnoreAHKInput = true;
  1505.             break;
  1506.         case 'M':
  1507.             g_input.TranscribeModifiedKeys = true;
  1508.             break;
  1509.         case 'L':
  1510.             // Use atoi() vs. ATOI() to avoid interpreting something like 0x01C as hex
  1511.             // when in fact the C was meant to be an option letter:
  1512.             g_input.BufferLengthMax = atoi(cp + 1);
  1513.             if (g_input.BufferLengthMax > INPUT_BUFFER_SIZE - 1)
  1514.                 g_input.BufferLengthMax = INPUT_BUFFER_SIZE - 1;
  1515.             break;
  1516.         case 'T':
  1517.             // Although ATOF() supports hex, it's been documented in the help file that hex should
  1518.             // not be used (see comment above) so if someone does it anyway, some option letters
  1519.             // might be misinterpreted:
  1520.             timeout = (int)(ATOF(cp + 1) * 1000);
  1521.             break;
  1522.         case 'V':
  1523.             g_input.Visible = true;
  1524.             break;
  1525.         case '*':
  1526.             g_input.FindAnywhere = true;
  1527.             break;
  1528.         }
  1529.     }
  1530.     // Point the global addresses to our memory areas on the stack:
  1531.     g_input.EndVK = end_vk;
  1532.     g_input.EndSC = end_sc;
  1533.     g_input.status = INPUT_IN_PROGRESS; // Signal the hook to start the input.
  1534.  
  1535.     // Make script persistent.  This is mostly for backward compatibility because it is documented behavior.
  1536.     // even though as of v1.0.42.03, the keyboard hook does not become permanent (which allows a subsequent
  1537.     // use of the commands Suspend/Hotkey to deinstall it, which seems to add flexibility/benefit).
  1538.     g_persistent = true;
  1539.     Hotkey::InstallKeybdHook(); // Install the hook (if needed).
  1540.  
  1541.     // A timer is used rather than monitoring the elapsed time here directly because
  1542.     // this script's quasi-thread might be interrupted by a Timer or Hotkey subroutine,
  1543.     // which (if it takes a long time) would result in our Input not obeying its timeout.
  1544.     // By using an actual timer, the TimerProc() will run when the timer expires regardless
  1545.     // of which quasi-thread is active, and it will end our input on schedule:
  1546.     if (timeout > 0)
  1547.         SET_INPUT_TIMER(timeout < 10 ? 10 : timeout)
  1548.  
  1549.     //////////////////////////////////////////////////////////////////
  1550.     // Wait for one of the following to terminate our input:
  1551.     // 1) The hook (due a match in aEndKeys or aMatchList);
  1552.     // 2) A thread that interrupts us with a new Input of its own;
  1553.     // 3) The timer we put in effect for our timeout (if we have one).
  1554.     //////////////////////////////////////////////////////////////////
  1555.     for (;;)
  1556.     {
  1557.         // Rather than monitoring the timeout here, just wait for the incoming WM_TIMER message
  1558.         // to take effect as a TimerProc() call during the MsgSleep():
  1559.         MsgSleep();
  1560.         if (g_input.status != INPUT_IN_PROGRESS)
  1561.             break;
  1562.     }
  1563.  
  1564.     switch(g_input.status)
  1565.     {
  1566.     case INPUT_TIMED_OUT:
  1567.         g_ErrorLevel->Assign("Timeout");
  1568.         break;
  1569.     case INPUT_TERMINATED_BY_MATCH:
  1570.         g_ErrorLevel->Assign("Match");
  1571.         break;
  1572.     case INPUT_TERMINATED_BY_ENDKEY:
  1573.     {
  1574.         char key_name[128] = "EndKey:";
  1575.         if (g_input.EndingRequiredShift)
  1576.         {
  1577.             // Since the only way a shift key can be required in our case is if it's a key whose name
  1578.             // is a single char (such as a shifted punctuation mark), use a diff. method to look up the
  1579.             // key name based on fact that the shift key was down to terminate the input.  We also know
  1580.             // that the key is an EndingVK because there's no way for the shift key to have been
  1581.             // required by a scan code based on the logic (above) that builds the end_key arrays.
  1582.             // MSDN: "Typically, ToAscii performs the translation based on the virtual-key code.
  1583.             // In some cases, however, bit 15 of the uScanCode parameter may be used to distinguish
  1584.             // between a key press and a key release. The scan code is used for translating ALT+
  1585.             // number key combinations.
  1586.             BYTE state[256] = {0};
  1587.             state[VK_SHIFT] |= 0x80; // Indicate that the neutral shift key is down for conversion purposes.
  1588.             Get_active_window_keybd_layout // Defines the variable active_window_keybd_layout for use below.
  1589.             int count = ToAsciiEx(g_input.EndingVK, vk_to_sc(g_input.EndingVK), (PBYTE)&state // Nothing is done about ToAsciiEx's dead key side-effects here because it seems to rare to be worth it (assuming its even a problem).
  1590.                 , (LPWORD)(key_name + 7), g_MenuIsVisible ? 1 : 0, active_window_keybd_layout); // v1.0.44.03: Changed to call ToAsciiEx() so that active window's layout can be specified (see hook.cpp for details).
  1591.             *(key_name + 7 + count) = '\0';  // Terminate the string.
  1592.         }
  1593.         else
  1594.             g_input.EndedBySC ? SCtoKeyName(g_input.EndingSC, key_name + 7, sizeof(key_name) - 7)
  1595.                 : VKtoKeyName(g_input.EndingVK, g_input.EndingSC, key_name + 7, sizeof(key_name) - 7);
  1596.         g_ErrorLevel->Assign(key_name);
  1597.         break;
  1598.     }
  1599.     case INPUT_LIMIT_REACHED:
  1600.         g_ErrorLevel->Assign("Max");
  1601.         break;
  1602.     default: // Our input was terminated due to a new input in a quasi-thread that interrupted ours.
  1603.         g_ErrorLevel->Assign("NewInput");
  1604.         break;
  1605.     }
  1606.  
  1607.     g_input.status = INPUT_OFF;  // See OVERVIEW above for why this must be set prior to returning.
  1608.  
  1609.     // In case it ended for reason other than a timeout, in which case the timer is still on:
  1610.     KILL_AND_PURGE_INPUT_TIMER
  1611.  
  1612.     // Seems ok to assign after the kill/purge above since input_buf is our own stack variable
  1613.     // and its contents shouldn't be affected even if KILL_AND_PURGE_INPUT_TIMER's MsgSleep()
  1614.     // results in a new thread being created that starts a new Input:
  1615.     return output_var->Assign(input_buf);
  1616. }
  1617.  
  1618.  
  1619.  
  1620. ResultType Line::PerformShowWindow(ActionTypeType aActionType, char *aTitle, char *aText
  1621.     , char *aExcludeTitle, char *aExcludeText)
  1622. {
  1623.     // By design, the WinShow command must always unhide a hidden window, even if the user has
  1624.     // specified that hidden windows should not be detected.  So set this now so that
  1625.     // DetermineTargetWindow() will make its calls in the right mode:
  1626.     bool need_restore = (aActionType == ACT_WINSHOW && !g.DetectHiddenWindows);
  1627.     if (need_restore)
  1628.         g.DetectHiddenWindows = true;
  1629.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  1630.     if (need_restore)
  1631.         g.DetectHiddenWindows = false;
  1632.     if (!target_window)
  1633.         return OK;
  1634.  
  1635.     // WinGroup's EnumParentActUponAll() is quite similar to the following, so the two should be
  1636.     // maintained together.
  1637.  
  1638.     int nCmdShow = SW_NONE; // Set default.
  1639.  
  1640.     switch (aActionType)
  1641.     {
  1642.     // SW_FORCEMINIMIZE: supported only in Windows 2000/XP and beyond: "Minimizes a window,
  1643.     // even if the thread that owns the window is hung. This flag should only be used when
  1644.     // minimizing windows from a different thread."
  1645.     // My: It seems best to use SW_FORCEMINIMIZE on OS's that support it because I have
  1646.     // observed ShowWindow() to hang (thus locking up our app's main thread) if the target
  1647.     // window is hung.
  1648.     // UPDATE: For now, not using "force" every time because it has undesirable side-effects such
  1649.     // as the window not being restored to its maximized state after it was minimized
  1650.     // this way.
  1651.     case ACT_WINMINIMIZE:
  1652.         if (IsWindowHung(target_window))
  1653.         {
  1654.             if (g_os.IsWin2000orLater())
  1655.                 nCmdShow = SW_FORCEMINIMIZE;
  1656.             //else it's not Win2k or later.  I have confirmed that SW_MINIMIZE can
  1657.             // lock up our thread on WinXP, which is why we revert to SW_FORCEMINIMIZE above.
  1658.             // Older/obsolete comment for background: don't attempt to minimize hung windows because that
  1659.             // might hang our thread because the call to ShowWindow() would never return.
  1660.         }
  1661.         else
  1662.             nCmdShow = SW_MINIMIZE;
  1663.         break;
  1664.     case ACT_WINMAXIMIZE: if (!IsWindowHung(target_window)) nCmdShow = SW_MAXIMIZE; break;
  1665.     case ACT_WINRESTORE:  if (!IsWindowHung(target_window)) nCmdShow = SW_RESTORE;  break;
  1666.     // Seems safe to assume it's not hung in these cases, since I'm inclined to believe
  1667.     // (untested) that hiding and showing a hung window won't lock up our thread, and
  1668.     // there's a chance they may be effective even against hung windows, unlike the
  1669.     // others above (except ACT_WINMINIMIZE, which has a special FORCE method):
  1670.     case ACT_WINHIDE: nCmdShow = SW_HIDE; break;
  1671.     case ACT_WINSHOW: nCmdShow = SW_SHOW; break;
  1672.     }
  1673.  
  1674.     // UPDATE:  Trying ShowWindowAsync()
  1675.     // now, which should avoid the problems with hanging.  UPDATE #2: Went back to
  1676.     // not using Async() because sometimes the script lines that come after the one
  1677.     // that is doing this action here rely on this action having been completed
  1678.     // (e.g. a window being maximized prior to clicking somewhere inside it).
  1679.     if (nCmdShow != SW_NONE)
  1680.     {
  1681.         // I'm not certain that SW_FORCEMINIMIZE works with ShowWindowAsync(), but
  1682.         // it probably does since there's absolutely no mention to the contrary
  1683.         // anywhere on MS's site or on the web.  But clearly, if it does work, it
  1684.         // does so only because Async() doesn't really post the message to the thread's
  1685.         // queue, instead opting for more aggresive measures.  Thus, it seems best
  1686.         // to do it this way to have maximum confidence in it:
  1687.         //if (nCmdShow == SW_FORCEMINIMIZE) // Safer not to use ShowWindowAsync() in this case.
  1688.             ShowWindow(target_window, nCmdShow);
  1689.         //else
  1690.         //    ShowWindowAsync(target_window, nCmdShow);
  1691. //PostMessage(target_window, WM_SYSCOMMAND, SC_MINIMIZE, 0);
  1692.         DoWinDelay;
  1693.     }
  1694.     return OK;  // Return success for all the above cases.
  1695. }
  1696.  
  1697.  
  1698.  
  1699. ResultType Line::PerformWait()
  1700. // Since other script threads can interrupt these commands while they're running, it's important that
  1701. // these commands not refer to sArgDeref[] and sArgVar[] anytime after an interruption becomes possible.
  1702. // This is because an interrupting thread usually changes the values to something inappropriate for this thread.
  1703. {
  1704.     bool wait_indefinitely;
  1705.     int sleep_duration;
  1706.     DWORD start_time;
  1707.  
  1708.     vk_type vk; // For GetKeyState.
  1709.     HANDLE running_process; // For RUNWAIT
  1710.     DWORD exit_code; // For RUNWAIT
  1711.  
  1712.     // For ACT_KEYWAIT:
  1713.     bool wait_for_keydown;
  1714.     KeyStateTypes key_state_type;
  1715.     JoyControls joy;
  1716.     int joystick_id;
  1717.     ExprTokenType token;
  1718.     char buf[LINE_SIZE];
  1719.  
  1720.     if (mActionType == ACT_RUNWAIT)
  1721.     {
  1722.         if (strcasestr(ARG3, "UseErrorLevel"))
  1723.         {
  1724.             if (!g_script.ActionExec(ARG1, NULL, ARG2, false, ARG3, &running_process, true, true, ARGVAR4))
  1725.                 return g_ErrorLevel->Assign("ERROR"); // See above comment for explanation.
  1726.             //else fall through to the waiting-phase of the operation.
  1727.             // Above: The special string ERROR is used, rather than a number like 1, because currently
  1728.             // RunWait might in the future be able to return any value, including 259 (STATUS_PENDING).
  1729.         }
  1730.         else // If launch fails, display warning dialog and terminate current thread.
  1731.             if (!g_script.ActionExec(ARG1, NULL, ARG2, true, ARG3, &running_process, false, true, ARGVAR4))
  1732.                 return FAIL;
  1733.             //else fall through to the waiting-phase of the operation.
  1734.     }
  1735.     
  1736.     // Must NOT use ELSE-IF in line below due to ELSE further down needing to execute for RunWait.
  1737.     if (mActionType == ACT_KEYWAIT)
  1738.     {
  1739.         if (   !(vk = TextToVK(ARG1))   )
  1740.         {
  1741.             if (   !(joy = (JoyControls)ConvertJoy(ARG1, &joystick_id))   ) // Not a valid key name.
  1742.                 // Indicate immediate timeout (if timeout was specified) or error.
  1743.                 return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1744.             if (!IS_JOYSTICK_BUTTON(joy)) // Currently, only buttons are supported.
  1745.                 return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1746.         }
  1747.         // Set defaults:
  1748.         wait_for_keydown = false;  // The default is to wait for the key to be released.
  1749.         key_state_type = KEYSTATE_PHYSICAL;  // Since physical is more often used.
  1750.         wait_indefinitely = true;
  1751.         sleep_duration = 0;
  1752.         for (char *cp = ARG2; *cp; ++cp)
  1753.         {
  1754.             switch(toupper(*cp))
  1755.             {
  1756.             case 'D':
  1757.                 wait_for_keydown = true;
  1758.                 break;
  1759.             case 'L':
  1760.                 key_state_type = KEYSTATE_LOGICAL;
  1761.                 break;
  1762.             case 'T':
  1763.                 // Although ATOF() supports hex, it's been documented in the help file that hex should
  1764.                 // not be used (see comment above) so if someone does it anyway, some option letters
  1765.                 // might be misinterpreted:
  1766.                 wait_indefinitely = false;
  1767.                 sleep_duration = (int)(ATOF(cp + 1) * 1000);
  1768.                 break;
  1769.             }
  1770.         }
  1771.         // The following must be set for ScriptGetJoyState():
  1772.         token.symbol = SYM_STRING;
  1773.         token.marker = buf;
  1774.     }
  1775.     else if (   (mActionType != ACT_RUNWAIT && mActionType != ACT_CLIPWAIT && *ARG3)
  1776.         || (mActionType == ACT_CLIPWAIT && *ARG1)   )
  1777.     {
  1778.         // Since the param containing the timeout value isn't blank, it must be numeric,
  1779.         // otherwise, the loading validation would have prevented the script from loading.
  1780.         wait_indefinitely = false;
  1781.         sleep_duration = (int)(ATOF(mActionType == ACT_CLIPWAIT ? ARG1 : ARG3) * 1000); // Can be zero.
  1782.         if (sleep_duration < 1)
  1783.             // Waiting 500ms in place of a "0" seems more useful than a true zero, which
  1784.             // doens't need to be supported because it's the same thing as something like
  1785.             // "IfWinExist".  A true zero for clipboard would be the same as
  1786.             // "IfEqual, clipboard, , xxx" (though admittedly it's higher overhead to
  1787.             // actually fetch the contents of the clipboard).
  1788.             sleep_duration = 500;
  1789.     }
  1790.     else
  1791.     {
  1792.         wait_indefinitely = true;
  1793.         sleep_duration = 0; // Just to catch any bugs.
  1794.     }
  1795.  
  1796.     if (mActionType != ACT_RUNWAIT)
  1797.         g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Set default ErrorLevel to be possibly overridden later on.
  1798.  
  1799.     bool any_clipboard_format = (mActionType == ACT_CLIPWAIT && ATOI(ARG2) == 1);
  1800.  
  1801.     // Right before starting the wait-loop, make a copy of our args using the stack
  1802.     // space in our recursion layer.  This is done in case other hotkey subroutine(s)
  1803.     // are launched while we're waiting here, which might cause our args to be overwritten
  1804.     // if any of them happen to be in the Deref buffer:
  1805.     char *arg[MAX_ARGS], *marker;
  1806.     int i, space_remaining;
  1807.     for (i = 0, space_remaining = LINE_SIZE, marker = buf; i < mArgc; ++i)
  1808.     {
  1809.         if (!space_remaining) // Realistically, should never happen.
  1810.             arg[i] = "";
  1811.         else
  1812.         {
  1813.             arg[i] = marker;  // Point it to its place in the buffer.
  1814.             strlcpy(marker, sArgDeref[i], space_remaining); // Make the copy.
  1815.             marker += strlen(marker) + 1;  // +1 for the zero terminator of each arg.
  1816.             space_remaining = (int)(LINE_SIZE - (marker - buf));
  1817.         }
  1818.     }
  1819.  
  1820.     for (start_time = GetTickCount();;) // start_time is initialized unconditionally for use with v1.0.30.02's new logging feature further below.
  1821.     { // Always do the first iteration so that at least one check is done.
  1822.         switch(mActionType)
  1823.         {
  1824.         case ACT_WINWAIT:
  1825.             #define SAVED_WIN_ARGS SAVED_ARG1, SAVED_ARG2, SAVED_ARG4, SAVED_ARG5
  1826.             if (WinExist(g, SAVED_WIN_ARGS, false, true))
  1827.             {
  1828.                 DoWinDelay;
  1829.                 return OK;
  1830.             }
  1831.             break;
  1832.         case ACT_WINWAITCLOSE:
  1833.             if (!WinExist(g, SAVED_WIN_ARGS))
  1834.             {
  1835.                 DoWinDelay;
  1836.                 return OK;
  1837.             }
  1838.             break;
  1839.         case ACT_WINWAITACTIVE:
  1840.             if (WinActive(g, SAVED_WIN_ARGS, true))
  1841.             {
  1842.                 DoWinDelay;
  1843.                 return OK;
  1844.             }
  1845.             break;
  1846.         case ACT_WINWAITNOTACTIVE:
  1847.             if (!WinActive(g, SAVED_WIN_ARGS, true))
  1848.             {
  1849.                 DoWinDelay;
  1850.                 return OK;
  1851.             }
  1852.             break;
  1853.         case ACT_CLIPWAIT:
  1854.             // Seems best to consider CF_HDROP to be a non-empty clipboard, since we
  1855.             // support the implicit conversion of that format to text:
  1856.             if (any_clipboard_format)
  1857.             {
  1858.                 if (CountClipboardFormats())
  1859.                     return OK;
  1860.             }
  1861.             else
  1862.                 if (IsClipboardFormatAvailable(CF_TEXT) || IsClipboardFormatAvailable(CF_HDROP))
  1863.                     return OK;
  1864.             break;
  1865.         case ACT_KEYWAIT:
  1866.             if (vk) // Waiting for key or mouse button, not joystick.
  1867.             {
  1868.                 if (ScriptGetKeyState(vk, key_state_type) == wait_for_keydown)
  1869.                     return OK;
  1870.             }
  1871.             else // Waiting for joystick button
  1872.             {
  1873.                 if ((bool)ScriptGetJoyState(joy, joystick_id, token, false) == wait_for_keydown)
  1874.                     return OK;
  1875.             }
  1876.             break;
  1877.         case ACT_RUNWAIT:
  1878.             // Pretty nasty, but for now, nothing is done to prevent an infinite loop.
  1879.             // In the future, maybe OpenProcess() can be used to detect if a process still
  1880.             // exists (is there any other way?):
  1881.             // MSDN: "Warning: If a process happens to return STILL_ACTIVE (259) as an error code,
  1882.             // applications that test for this value could end up in an infinite loop."
  1883.             if (running_process)
  1884.                 GetExitCodeProcess(running_process, &exit_code);
  1885.             else // it can be NULL in the case of launching things like "find D:\" or "www.yahoo.com"
  1886.                 exit_code = 0;
  1887.             if (exit_code != STATUS_PENDING) // STATUS_PENDING == STILL_ACTIVE
  1888.             {
  1889.                 if (running_process)
  1890.                     CloseHandle(running_process);
  1891.                 // Use signed vs. unsigned, since that is more typical?  No, it seems better
  1892.                 // to use unsigned now that script variables store 64-bit ints.  This is because
  1893.                 // GetExitCodeProcess() yields a DWORD, implying that the value should be unsigned.
  1894.                 // Unsigned also is more useful in cases where an app returns a (potentially large)
  1895.                 // count of something as its result.  However, if this is done, it won't be easy
  1896.                 // to check against a return value of -1, for example, which I suspect many apps
  1897.                 // return.  AutoIt3 (and probably 2) use a signed int as well, so that is another
  1898.                 // reason to keep it this way:
  1899.                 return g_ErrorLevel->Assign((int)exit_code);
  1900.             }
  1901.             break;
  1902.         }
  1903.  
  1904.         // Must cast to int or any negative result will be lost due to DWORD type:
  1905.         if (wait_indefinitely || (int)(sleep_duration - (GetTickCount() - start_time)) > SLEEP_INTERVAL_HALF)
  1906.         {
  1907.             if (MsgSleep(INTERVAL_UNSPECIFIED)) // INTERVAL_UNSPECIFIED performs better.
  1908.             {
  1909.                 // v1.0.30.02: Since MsgSleep() launched and returned from at least one new thread, put the
  1910.                 // current waiting line into the line-log again to make it easy to see what the current
  1911.                 // thread is doing.  This is especially useful for figuring out which subroutine is holding
  1912.                 // another thread interrupted beneath it.  For example, if a timer gets interrupted by
  1913.                 // a hotkey that has an indefinite WinWait, and that window never appears, this will allow
  1914.                 // the user to find out the culprit thread by showing its line in the log (and usually
  1915.                 // it will appear as the very last line, since usually the script is idle and thus the
  1916.                 // currently active thread is the one that's still waiting for the window).
  1917.                 sLog[sLogNext] = this;
  1918.                 sLogTick[sLogNext++] = start_time; // Store a special value so that Line::LogToText() can report that its "still waiting" from earlier.
  1919.                 if (sLogNext >= LINE_LOG_SIZE)
  1920.                     sLogNext = 0;
  1921.                 // The lines above are the similar to those used in ExecUntil(), so the two should be
  1922.                 // maintained together.
  1923.             }
  1924.         }
  1925.         else // Done waiting.
  1926.             return g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Since it timed out, we override the default with this.
  1927.     } // for()
  1928. }
  1929.  
  1930.  
  1931.  
  1932. ResultType Line::WinMove(char *aTitle, char *aText, char *aX, char *aY
  1933.     , char *aWidth, char *aHeight, char *aExcludeTitle, char *aExcludeText)
  1934. {
  1935.     // So that compatibility is retained, don't set ErrorLevel for commands that are native to AutoIt2
  1936.     // but that AutoIt2 doesn't use ErrorLevel with (such as this one).
  1937.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  1938.     if (!target_window)
  1939.         return OK;
  1940.     RECT rect;
  1941.     if (!GetWindowRect(target_window, &rect))
  1942.         return OK;  // Can't set errorlevel, see above.
  1943.     MoveWindow(target_window
  1944.         , *aX && stricmp(aX, "default") ? ATOI(aX) : rect.left  // X-position
  1945.         , *aY && stricmp(aY, "default") ? ATOI(aY) : rect.top   // Y-position
  1946.         , *aWidth && stricmp(aWidth, "default") ? ATOI(aWidth) : rect.right - rect.left
  1947.         , *aHeight && stricmp(aHeight, "default") ? ATOI(aHeight) : rect.bottom - rect.top
  1948.         , TRUE);  // Do repaint.
  1949.     DoWinDelay;
  1950.     return OK;
  1951. }
  1952.  
  1953.  
  1954.  
  1955. ResultType Line::ControlSend(char *aControl, char *aKeysToSend, char *aTitle, char *aText
  1956.     , char *aExcludeTitle, char *aExcludeText, bool aSendRaw)
  1957. {
  1958.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  1959.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  1960.     if (!target_window)
  1961.         return OK;
  1962.     HWND control_window = stricmp(aControl, "ahk_parent")
  1963.         ? ControlExist(target_window, aControl) // This can return target_window itself for cases such as ahk_id %ControlHWND%.
  1964.         : target_window;
  1965.     if (!control_window)
  1966.         return OK;
  1967.     SendKeys(aKeysToSend, aSendRaw, SM_EVENT, control_window);
  1968.     // But don't do WinDelay because KeyDelay should have been in effect for the above.
  1969.     return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  1970. }
  1971.  
  1972.  
  1973.  
  1974. ResultType Line::ControlClick(vk_type aVK, int aClickCount, char *aOptions, char *aControl
  1975.     , char *aTitle, char *aText, char *aExcludeTitle, char *aExcludeText)
  1976. {
  1977.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  1978.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  1979.     if (!target_window)
  1980.         return OK;
  1981.  
  1982.     // Set the defaults that will be in effect unless overridden by options:
  1983.     KeyEventTypes event_type = KEYDOWNANDUP;
  1984.     bool position_mode = false;
  1985.     bool do_activate = true;
  1986.     // These default coords can be overridden either by aOptions or aControl's X/Y mode:
  1987.     POINT click = {COORD_UNSPECIFIED, COORD_UNSPECIFIED};
  1988.  
  1989.     for (char *cp = aOptions; *cp; ++cp)
  1990.     {
  1991.         switch(toupper(*cp))
  1992.         {
  1993.         case 'D':
  1994.             event_type = KEYDOWN;
  1995.             break;
  1996.         case 'U':
  1997.             event_type = KEYUP;
  1998.             break;
  1999.         case 'N':
  2000.             // v1.0.45:
  2001.             // It was reported (and confirmed through testing) that this new NA mode (which avoids
  2002.             // AttachThreadInput() and SetActiveWindow()) improves the reliability of ControlClick when
  2003.             // the user is moving the mouse fairly quickly at the time the command tries to click a button.
  2004.             // In addition, the new mode avoids activating the window, which tends to happen otherwise.
  2005.             // HOWEVER, the new mode seems no more reliable than the old mode when the target window is
  2006.             // the active window.  In addition, there may be side-effects of the new mode (I caught it
  2007.             // causing Notepad's Save-As dialog to hang once, during the display of its "Overwrite?" dialog).
  2008.             // ALSO, SetControlDelay -1 seems to fix the unreliability issue as well (independently of NA),
  2009.             // though it might not work with some types of windows/controls (thus, for backward
  2010.             // compatibility, ControlClick still obeys SetControlDelay).
  2011.             if (toupper(cp[1]) == 'A')
  2012.             {
  2013.                 cp += 1;  // Add 1 vs. 2 to skip over the rest of the letters in this option word.
  2014.                 do_activate = false;
  2015.             }
  2016.             break;
  2017.         case 'P':
  2018.             if (!strnicmp(cp, "Pos", 3))
  2019.             {
  2020.                 cp += 2;  // Add 2 vs. 3 to skip over the rest of the letters in this option word.
  2021.                 position_mode = true;
  2022.             }
  2023.             break;
  2024.         // For the below:
  2025.         // Use atoi() vs. ATOI() to avoid interpreting something like 0x01D as hex
  2026.         // when in fact the D was meant to be an option letter:
  2027.         case 'X':
  2028.             click.x = atoi(cp + 1); // Will be overridden later below if it turns out that position_mode is in effect.
  2029.             break;
  2030.         case 'Y':
  2031.             click.y = atoi(cp + 1); // Will be overridden later below if it turns out that position_mode is in effect.
  2032.             break;
  2033.         }
  2034.     }
  2035.  
  2036.     // It's debatable, but might be best for flexibility (and backward compatbility) to allow target_window to itself
  2037.     // be a control (at least for the position_mode handler below).  For example, the script may have called SetParent
  2038.     // to make a top-level window the child of some other window, in which case this policy allows it to be seen like
  2039.     // a non-child.
  2040.     HWND control_window = position_mode ? NULL : ControlExist(target_window, aControl); // This can return target_window itself for cases such as ahk_id %ControlHWND%.
  2041.     if (!control_window) // Even if position_mode is false, the below is still attempted, as documented.
  2042.     {
  2043.         // New section for v1.0.24.  But only after the above fails to find a control do we consider
  2044.         // whether aControl contains X and Y coordinates.  That way, if a control class happens to be
  2045.         // named something like "X1 Y1", it will still be found by giving precedence to class names.
  2046.         point_and_hwnd_type pah = {0};
  2047.         // Parse the X an Y coordinates in a strict way to reduce ambiguity with control names and also
  2048.         // to keep the code simple.
  2049.         char *cp = omit_leading_whitespace(aControl);
  2050.         if (toupper(*cp) != 'X')
  2051.             return OK; // Let ErrorLevel tell the story.
  2052.         ++cp;
  2053.         if (!*cp)
  2054.             return OK;
  2055.         pah.pt.x = ATOI(cp);
  2056.         if (   !(cp = StrChrAny(cp, " \t"))   ) // Find next space or tab (there must be one for it to be considered valid).
  2057.             return OK;
  2058.         cp = omit_leading_whitespace(cp + 1);
  2059.         if (!*cp || toupper(*cp) != 'Y')
  2060.             return OK;
  2061.         ++cp;
  2062.         if (!*cp)
  2063.             return OK;
  2064.         pah.pt.y = ATOI(cp);
  2065.         // The passed-in coordinates are always relative to target_window's upper left corner because offering
  2066.         // an option for absolute/screen coordinates doesn't seem useful.
  2067.         RECT rect;
  2068.         GetWindowRect(target_window, &rect);
  2069.         pah.pt.x += rect.left; // Convert to screen coordinates.
  2070.         pah.pt.y += rect.top;
  2071.         EnumChildWindows(target_window, EnumChildFindPoint, (LPARAM)&pah); // Find topmost control containing point.
  2072.         // If no control is at this point, try posting the mouse event message(s) directly to the
  2073.         // parent window to increase the flexibility of this feature:
  2074.         control_window = pah.hwnd_found ? pah.hwnd_found : target_window;
  2075.         // Convert click's target coordinates to be relative to the client area of the control or
  2076.         // parent window because that is the format required by messages such as WM_LBUTTONDOWN
  2077.         // used later below:
  2078.         click = pah.pt;
  2079.         ScreenToClient(control_window, &click);
  2080.     }
  2081.  
  2082.     // This is done this late because it seems better to set an ErrorLevel of 1 (above) whenever the
  2083.     // target window or control isn't found, or any other error condition occurs above:
  2084.     if (aClickCount < 1)
  2085.         // Allow this to simply "do nothing", because it increases flexibility
  2086.         // in the case where the number of clicks is a dereferenced script variable
  2087.         // that may sometimes (by intent) resolve to zero or negative:
  2088.         return g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  2089.  
  2090.     RECT rect;
  2091.     if (click.x == COORD_UNSPECIFIED || click.y == COORD_UNSPECIFIED)
  2092.     {
  2093.         // The following idea is from AutoIt3. It states: "Get the dimensions of the control so we can click
  2094.         // the centre of it" (maybe safer and more natural than 0,0).
  2095.         // My: In addition, this is probably better for some large controls (e.g. SysListView32) because
  2096.         // clicking at 0,0 might activate a part of the control that is not even visible:
  2097.         if (!GetWindowRect(control_window, &rect))
  2098.             return OK;  // Let ErrorLevel tell the story.
  2099.         if (click.x == COORD_UNSPECIFIED)
  2100.             click.x = (rect.right - rect.left) / 2;
  2101.         if (click.y == COORD_UNSPECIFIED)
  2102.             click.y = (rect.bottom - rect.top) / 2;
  2103.     }
  2104.     LPARAM lparam = MAKELPARAM(click.x, click.y);
  2105.  
  2106.     UINT msg_down, msg_up;
  2107.     WPARAM wparam;
  2108.     bool vk_is_wheel = aVK == VK_WHEEL_UP || aVK == VK_WHEEL_DOWN;
  2109.  
  2110.     if (vk_is_wheel)
  2111.     {
  2112.         wparam = (aClickCount * ((aVK == VK_WHEEL_UP) ? WHEEL_DELTA : -WHEEL_DELTA)) << 16;  // High order word contains the delta.
  2113.         // Make the event more accurate by having the state of the keys reflected in the event.
  2114.         // The logical state (not physical state) of the modifier keys is used so that something
  2115.         // like this is supported:
  2116.         // Send, {ShiftDown}
  2117.         // MouseClick, WheelUp
  2118.         // Send, {ShiftUp}
  2119.         // In addition, if the mouse hook is installed, use its logical mouse button state so that
  2120.         // something like this is supported:
  2121.         // MouseClick, left, , , , , D  ; Hold down the left mouse button
  2122.         // MouseClick, WheelUp
  2123.         // MouseClick, left, , , , , U  ; Release the left mouse button.
  2124.         // UPDATE: Since the other ControlClick types (such as leftclick) do not reflect these
  2125.         // modifiers -- and we want to keep it that way, at least by default, for compatibility
  2126.         // reasons -- it seems best for consistency not to do them for WheelUp/Down either.
  2127.         // A script option can be added in the future to obey the state of the modifiers:
  2128.         //mod_type mod = GetModifierState();
  2129.         //if (mod & MOD_SHIFT)
  2130.         //    wparam |= MK_SHIFT;
  2131.         //if (mod & MOD_CONTROL)
  2132.         //    wparam |= MK_CONTROL;
  2133.         //if (g_MouseHook)
  2134.         //    wparam |= g_mouse_buttons_logical;
  2135.     }
  2136.     else
  2137.     {
  2138.         switch (aVK)
  2139.         {
  2140.             case VK_LBUTTON:  msg_down = WM_LBUTTONDOWN; msg_up = WM_LBUTTONUP; wparam = MK_LBUTTON; break;
  2141.             case VK_RBUTTON:  msg_down = WM_RBUTTONDOWN; msg_up = WM_RBUTTONUP; wparam = MK_RBUTTON; break;
  2142.             case VK_MBUTTON:  msg_down = WM_MBUTTONDOWN; msg_up = WM_MBUTTONUP; wparam = MK_MBUTTON; break;
  2143.             case VK_XBUTTON1: msg_down = WM_XBUTTONDOWN; msg_up = WM_XBUTTONUP; wparam = MK_XBUTTON1; break;
  2144.             case VK_XBUTTON2: msg_down = WM_XBUTTONDOWN; msg_up = WM_XBUTTONUP; wparam = MK_XBUTTON2; break;
  2145.             default: return OK; // Just do nothing since this should realistically never happen.
  2146.         }
  2147.     }
  2148.  
  2149.     // SetActiveWindow() requires ATTACH_THREAD_INPUT to succeed.  Even though the MSDN docs state
  2150.     // that SetActiveWindow() has no effect unless the parent window is foreground, Jon insists
  2151.     // that SetActiveWindow() resolved some problems for some users.  In any case, it seems best
  2152.     // to do this in case the window really is foreground, in which case MSDN indicates that
  2153.     // it will help for certain types of dialogs.
  2154.     ATTACH_THREAD_INPUT_AND_SETACTIVEWINDOW_IF_DO_ACTIVATE  // It's kept with a similar macro for maintainability.
  2155.     // v1.0.44.13: Notes for the above: Unlike some other Control commands, GetNonChildParent() is not
  2156.     // called here when target_window==control_window.  This is because the script may have called
  2157.     // SetParent to make target_window the child of some other window, in which case target_window
  2158.     // should still be used above (unclear).  Perhaps more importantly, it's allowed for control_window
  2159.     // to be the same as target_window, at least in position_mode, whose docs state, "If there is no
  2160.     // control, the target window itself will be sent the event (which might have no effect depending
  2161.     // on the nature of the window)."  In other words, it seems too complicated and rare to add explicit
  2162.     // handling for "ahk_id %ControlHWND%" (though the below rules should work).
  2163.     // The line "ControlClick,, ahk_id %HWND%" can have mulitple meanings depending on the nature of HWND:
  2164.     // 1) If HWND is a top-level window, its topmost child will be clicked.
  2165.     // 2) If HWND is a top-level window that has become a child of another window via SetParent: same.
  2166.     // 3) If HWND is a control, its topmost child will be clicked (or itself if it has no children).
  2167.     //    For example, the following works (as documented in the first parameter):
  2168.     //    ControlGet, HWND, HWND,, OK, A  ; Get the HWND of the OK button.
  2169.     //    ControlClick,, ahk_id %HWND%
  2170.  
  2171.     if (vk_is_wheel)
  2172.     {
  2173.         PostMessage(control_window, WM_MOUSEWHEEL, wparam, lparam);
  2174.         DoControlDelay;
  2175.     }
  2176.     else
  2177.     {
  2178.         for (int i = 0; i < aClickCount; ++i)
  2179.         {
  2180.             if (event_type != KEYUP) // It's either down-only or up-and-down so always to the down-event.
  2181.             {
  2182.                 PostMessage(control_window, msg_down, wparam, lparam);
  2183.                 // Seems best to do this one too, which is what AutoIt3 does also.  User can always reduce
  2184.                 // ControlDelay to 0 or -1.  Update: Jon says this delay might be causing it to fail in
  2185.                 // some cases.  Upon reflection, it seems best not to do this anyway because PostMessage()
  2186.                 // should queue up the message for the app correctly even if it's busy.  Update: But I
  2187.                 // think the timestamp is available on every posted message, so if some apps check for
  2188.                 // inhumanly fast clicks (to weed out transients with partial clicks of the mouse, or
  2189.                 // to detect artificial input), the click might not work.  So it might be better after
  2190.                 // all to do the delay until it's proven to be problematic (Jon implies that he has
  2191.                 // no proof yet).  IF THIS IS EVER DISABLED, be sure to do the ControlDelay anyway
  2192.                 // if event_type == KEYDOWN:
  2193.                 DoControlDelay;
  2194.             }
  2195.             if (event_type != KEYDOWN) // It's either up-only or up-and-down so always to the up-event.
  2196.             {
  2197.                 PostMessage(control_window, msg_up, 0, lparam);
  2198.                 DoControlDelay;
  2199.             }
  2200.         }
  2201.     }
  2202.  
  2203.     DETACH_THREAD_INPUT  // Also takes into account do_activate, indirectly.
  2204.  
  2205.     return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  2206. }
  2207.  
  2208.  
  2209.  
  2210. ResultType Line::ControlMove(char *aControl, char *aX, char *aY, char *aWidth, char *aHeight
  2211.     , char *aTitle, char *aText, char *aExcludeTitle, char *aExcludeText)
  2212. {
  2213.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  2214.     if (!target_window)
  2215.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  2216.     HWND control_window = ControlExist(target_window, aControl); // This can return target_window itself for cases such as ahk_id %ControlHWND%.
  2217.     if (!control_window)
  2218.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  2219.  
  2220.     POINT point;
  2221.     point.x = *aX ? ATOI(aX) : COORD_UNSPECIFIED;
  2222.     point.y = *aY ? ATOI(aY) : COORD_UNSPECIFIED;
  2223.  
  2224.     // First convert the user's given coordinates -- which by default are relative to the window's
  2225.     // upper left corner -- to screen coordinates:
  2226.     if (point.x != COORD_UNSPECIFIED || point.y != COORD_UNSPECIFIED)
  2227.     {
  2228.         RECT rect;
  2229.         // v1.0.44.13: Below was fixed to allow for the fact that target_window might be the control
  2230.         // itself (e.g. via ahk_id %ControlHWND%).  For consistency with ControlGetPos and other things,
  2231.         // it seems best to call GetNonChildParent rather than GetParent(); for example, a Tab control
  2232.         // that contains a child window that in turn contains the actual controls should probably report
  2233.         // the position of each control relative to the dialog itself rather than the tab control or its
  2234.         // master window.  The lost argument in favor of GetParent is that it seems more flexible, such
  2235.         // as cases where the script has called SetParent() to make a top-level window the child of some
  2236.         // other window, in which case the target control's immediate parent should be used, not its most
  2237.         // distant ancestor. This might also be desirable for controls that are children of other controls,
  2238.         // such as as Combobox's Edit.
  2239.         if (!GetWindowRect(target_window == control_window ? GetNonChildParent(target_window) : target_window
  2240.             , &rect))
  2241.             return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  2242.         if (point.x != COORD_UNSPECIFIED)
  2243.             point.x += rect.left;
  2244.         if (point.y != COORD_UNSPECIFIED)
  2245.             point.y += rect.top;
  2246.     }
  2247.  
  2248.     // If either coordinate is unspecified, put the control's current screen coordinate(s)
  2249.     // into point:
  2250.     RECT control_rect;
  2251.     if (!GetWindowRect(control_window, &control_rect))
  2252.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  2253.     if (point.x == COORD_UNSPECIFIED)
  2254.         point.x = control_rect.left;
  2255.     if (point.y == COORD_UNSPECIFIED)
  2256.         point.y = control_rect.top;
  2257.  
  2258.     // Use the immediate parent since controls can themselves have child controls:
  2259.     HWND immediate_parent = GetParent(control_window);
  2260.     if (!immediate_parent)
  2261.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  2262.  
  2263.     // Convert from absolute screen coordinates to coordinates used with MoveWindow(),
  2264.     // which are relative to control_window's parent's client area:
  2265.     if (!ScreenToClient(immediate_parent, &point))
  2266.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  2267.  
  2268.     MoveWindow(control_window
  2269.         , point.x
  2270.         , point.y
  2271.         , *aWidth ? ATOI(aWidth) : control_rect.right - control_rect.left
  2272.         , *aHeight ? ATOI(aHeight) : control_rect.bottom - control_rect.top
  2273.         , TRUE);  // Do repaint.
  2274.  
  2275.     DoControlDelay
  2276.     return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  2277. }
  2278.  
  2279.  
  2280.  
  2281. ResultType Line::ControlGetPos(char *aControl, char *aTitle, char *aText, char *aExcludeTitle, char *aExcludeText)
  2282. {
  2283.     Var *output_var_x = ARGVAR1;  // Ok if NULL.
  2284.     Var *output_var_y = ARGVAR2;  // Ok if NULL.
  2285.     Var *output_var_width = ARGVAR3;  // Ok if NULL.
  2286.     Var *output_var_height = ARGVAR4;  // Ok if NULL.
  2287.  
  2288.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  2289.     HWND control_window = target_window ? ControlExist(target_window, aControl) : NULL; // This can return target_window itself for cases such as ahk_id %ControlHWND%.
  2290.     if (!control_window)
  2291.     {
  2292.         if (output_var_x)
  2293.             output_var_x->Assign();
  2294.         if (output_var_y)
  2295.             output_var_y->Assign();
  2296.         if (output_var_width)
  2297.             output_var_width->Assign();
  2298.         if (output_var_height)
  2299.             output_var_height->Assign();
  2300.         return OK;
  2301.     }
  2302.  
  2303.     RECT parent_rect, child_rect;
  2304.     // Realistically never fails since DetermineTargetWindow() and ControlExist() should always yield
  2305.     // valid window handles:
  2306.     GetWindowRect(target_window == control_window ? GetNonChildParent(target_window) : target_window
  2307.         , &parent_rect); // v1.0.44.13: Above was fixed to allow for the fact that target_window might be the control itself (e.g. via ahk_id %ControlHWND%).  See ControlMove for details.
  2308.     GetWindowRect(control_window, &child_rect);
  2309.  
  2310.     if (output_var_x && !output_var_x->Assign(child_rect.left - parent_rect.left))
  2311.         return FAIL;
  2312.     if (output_var_y && !output_var_y->Assign(child_rect.top - parent_rect.top))
  2313.         return FAIL;
  2314.     if (output_var_width && !output_var_width->Assign(child_rect.right - child_rect.left))
  2315.         return FAIL;
  2316.     if (output_var_height && !output_var_height->Assign(child_rect.bottom - child_rect.top))
  2317.         return FAIL;
  2318.  
  2319.     return OK;
  2320. }
  2321.  
  2322.  
  2323.  
  2324. ResultType Line::ControlGetFocus(char *aTitle, char *aText, char *aExcludeTitle, char *aExcludeText)
  2325. {
  2326.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  2327.     OUTPUT_VAR->Assign();  // Set default: blank for the output variable.
  2328.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  2329.     if (!target_window)
  2330.         return OK;  // Let ErrorLevel and the blank output variable tell the story.
  2331.  
  2332.     // Unlike many of the other Control commands, this one requires AttachThreadInput().
  2333.     ATTACH_THREAD_INPUT
  2334.  
  2335.     class_and_hwnd_type cah;
  2336.     cah.hwnd = GetFocus();  // Do this now that our thread is attached to the target window's.
  2337.  
  2338.     // Very important to detach any threads whose inputs were attached above,
  2339.     // prior to returning, otherwise the next attempt to attach thread inputs
  2340.     // for these particular windows may result in a hung thread or other
  2341.     // undesirable effect:
  2342.     DETACH_THREAD_INPUT
  2343.  
  2344.     if (!cah.hwnd)
  2345.         return OK;  // Let ErrorLevel and the blank output variable tell the story.
  2346.  
  2347.     char class_name[WINDOW_CLASS_SIZE];
  2348.     cah.class_name = class_name;
  2349.     if (!GetClassName(cah.hwnd, class_name, sizeof(class_name) - 5)) // -5 to allow room for sequence number.
  2350.         return OK;  // Let ErrorLevel and the blank output variable tell the story.
  2351.     
  2352.     cah.class_count = 0;  // Init for the below.
  2353.     cah.is_found = false; // Same.
  2354.     EnumChildWindows(target_window, EnumChildFindSeqNum, (LPARAM)&cah);
  2355.     if (!cah.is_found)
  2356.         return OK;  // Let ErrorLevel and the blank output variable tell the story.
  2357.     // Append the class sequence number onto the class name set the output param to be that value:
  2358.     snprintfcat(class_name, sizeof(class_name), "%d", cah.class_count);
  2359.     g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  2360.     return OUTPUT_VAR->Assign(class_name);
  2361. }
  2362.  
  2363.  
  2364.  
  2365. BOOL CALLBACK EnumChildFindSeqNum(HWND aWnd, LPARAM lParam)
  2366. {
  2367.     class_and_hwnd_type &cah = *(class_and_hwnd_type *)lParam;  // For performance and convenience.
  2368.     char class_name[WINDOW_CLASS_SIZE];
  2369.     if (!GetClassName(aWnd, class_name, sizeof(class_name)))
  2370.         return TRUE;  // Continue the enumeration.
  2371.     if (!strcmp(class_name, cah.class_name)) // Class names match.
  2372.     {
  2373.         ++cah.class_count;
  2374.         if (aWnd == cah.hwnd)  // The caller-specified window has been found.
  2375.         {
  2376.             cah.is_found = true;
  2377.             return FALSE;
  2378.         }
  2379.     }
  2380.     return TRUE; // Continue enumeration until a match is found or there aren't any windows remaining.
  2381. }
  2382.  
  2383.  
  2384.  
  2385. ResultType Line::ControlFocus(char *aControl, char *aTitle, char *aText
  2386.     , char *aExcludeTitle, char *aExcludeText)
  2387. {
  2388.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  2389.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  2390.     if (!target_window)
  2391.         return OK;
  2392.     HWND control_window = ControlExist(target_window, aControl); // This can return target_window itself for cases such as ahk_id %ControlHWND%.
  2393.     if (!control_window)
  2394.         return OK;
  2395.  
  2396.     // Unlike many of the other Control commands, this one requires AttachThreadInput()
  2397.     // to have any realistic chance of success (though sometimes it may work by pure
  2398.     // chance even without it):
  2399.     ATTACH_THREAD_INPUT
  2400.  
  2401.     if (SetFocus(control_window))
  2402.     {
  2403.         g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  2404.         DoControlDelay;
  2405.     }
  2406.  
  2407.     // Very important to detach any threads whose inputs were attached above,
  2408.     // prior to returning, otherwise the next attempt to attach thread inputs
  2409.     // for these particular windows may result in a hung thread or other
  2410.     // undesirable effect:
  2411.     DETACH_THREAD_INPUT
  2412.  
  2413.     return OK;
  2414. }
  2415.  
  2416.  
  2417.  
  2418. ResultType Line::ControlSetText(char *aControl, char *aNewText, char *aTitle, char *aText
  2419.     , char *aExcludeTitle, char *aExcludeText)
  2420. {
  2421.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  2422.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  2423.     if (!target_window)
  2424.         return OK;
  2425.     HWND control_window = ControlExist(target_window, aControl); // This can return target_window itself for cases such as ahk_id %ControlHWND%.
  2426.     if (!control_window)
  2427.         return OK;
  2428.     // SendMessage must be used, not PostMessage(), at least for some (probably most) apps.
  2429.     // Also: No need to call IsWindowHung() because SendMessageTimeout() should return
  2430.     // immediately if the OS already "knows" the window is hung:
  2431.     DWORD result;
  2432.     SendMessageTimeout(control_window, WM_SETTEXT, (WPARAM)0, (LPARAM)aNewText
  2433.         , SMTO_ABORTIFHUNG, 5000, &result);
  2434.     DoControlDelay;
  2435.     return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  2436. }
  2437.  
  2438.  
  2439.  
  2440. ResultType Line::ControlGetText(char *aControl, char *aTitle, char *aText
  2441.     , char *aExcludeTitle, char *aExcludeText)
  2442. {
  2443.     Var &output_var = *OUTPUT_VAR;
  2444.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR);  // Set default.
  2445.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  2446.     HWND control_window = target_window ? ControlExist(target_window, aControl) : NULL; // This can return target_window itself for cases such as ahk_id %ControlHWND%.
  2447.     // Even if control_window is NULL, we want to continue on so that the output
  2448.     // param is set to be the empty string, which is the proper thing to do
  2449.     // rather than leaving whatever was in there before.
  2450.  
  2451.     // Handle the output parameter.  This section is similar to that in
  2452.     // PerformAssign().  Note: Using GetWindowTextTimeout() vs. GetWindowText()
  2453.     // because it is able to get text from more types of controls (e.g. large edit controls):
  2454.     VarSizeType space_needed = control_window ? GetWindowTextTimeout(control_window) + 1 : 1; // 1 for terminator.
  2455.     if (space_needed > g_MaxVarCapacity) // Allow the command to succeed by truncating the text.
  2456.         space_needed = g_MaxVarCapacity;
  2457.  
  2458.     // Set up the var, enlarging it if necessary.  If the output_var is of type VAR_CLIPBOARD,
  2459.     // this call will set up the clipboard for writing:
  2460.     if (output_var.Assign(NULL, space_needed - 1) != OK)
  2461.         return FAIL;  // It already displayed the error.
  2462.     // Fetch the text directly into the var.  Also set the length explicitly
  2463.     // in case actual size written was off from the esimated size (since
  2464.     // GetWindowTextLength() can return more space that will actually be required
  2465.     // in certain circumstances, see MS docs):
  2466.     if (control_window)
  2467.     {
  2468.         if (   !(output_var.Length() = (VarSizeType)GetWindowTextTimeout(control_window
  2469.             , output_var.Contents(), space_needed))   ) // There was no text to get or GetWindowTextTimeout() failed.
  2470.             *output_var.Contents() = '\0';  // Safe because Assign() gave us a non-constant memory area.
  2471.         g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  2472.     }
  2473.     else
  2474.     {
  2475.         *output_var.Contents() = '\0';
  2476.         output_var.Length() = 0;
  2477.         // And leave g_ErrorLevel set to ERRORLEVEL_ERROR to distinguish a non-existent control
  2478.         // from a one that does exist but returns no text.
  2479.     }
  2480.     // Consider the above to be always successful, even if the window wasn't found, except
  2481.     // when below returns an error:
  2482.     return output_var.Close();  // In case it's the clipboard.
  2483. }
  2484.  
  2485.  
  2486.  
  2487. ResultType Line::ControlGetListView(Var &aOutputVar, HWND aHwnd, char *aOptions)
  2488. // Called by ControlGet() below.  It has ensured that aHwnd is a valid handle to a ListView.
  2489. // It has also initialized g_ErrorLevel to be ERRORLEVEL_ERROR, which will be overridden
  2490. // if we succeed here.
  2491. {
  2492.     aOutputVar.Assign(); // Init to blank in case of early return.  Caller has already initialized g_ErrorLevel for us.
  2493.  
  2494.     // GET ROW COUNT
  2495.     LRESULT row_count;
  2496.     if (!SendMessageTimeout(aHwnd, LVM_GETITEMCOUNT, 0, 0, SMTO_ABORTIFHUNG, 2000, (PDWORD_PTR)&row_count)) // Timed out or failed.
  2497.         return OK;  // Let ErrorLevel tell the story.
  2498.  
  2499.     // GET COLUMN COUNT
  2500.     // Through testing, could probably get to a level of 90% certainty that a ListView for which
  2501.     // InsertColumn() was never called (or was called only once) might lack a header control if the LV is
  2502.     // created in List/Icon view-mode and/or with LVS_NOCOLUMNHEADER. The problem is that 90% doesn't
  2503.     // seem to be enough to justify elimination of the code for "undetermined column count" mode.  If it
  2504.     // ever does become a certainty, the following could be changed:
  2505.     // 1) The extra code for "undetermined" mode rather than simply forcing col_count to be 1.
  2506.     // 2) Probably should be kept for compatibility: -1 being returned when undetermined "col count".
  2507.     //
  2508.     // The following approach might be the only simple yet reliable way to get the column count (sending
  2509.     // LVM_GETITEM until it returns false doesn't work because it apparently returns true even for
  2510.     // nonexistent subitems -- the same is reported to happen with LVM_GETCOLUMN and such, though I seem
  2511.     // to remember that LVM_SETCOLUMN fails on non-existent columns -- but calling that on a ListView
  2512.     // that isn't in Report view has been known to traumatize the control).
  2513.     // Fix for v1.0.37.01: It appears that the header doesn't always exist.  For example, when an
  2514.     // Explorer window opens and is *initially* in icon or list mode vs. details/tiles mode, testing
  2515.     // shows that there is no header control.  Testing also shows that there is exactly one column
  2516.     // in such cases but only for Explorer and other things that avoid creating the invisible columns.
  2517.     // For example, a script can create a ListView in Icon-mode and give it retrievable column data for
  2518.     // columns beyond the first.  Thus, having the undetermined-col-count mode preserves flexibility
  2519.     // by allowing individual columns beyond the first to be retrieved.  On a related note, testing shows
  2520.     // that attempts to explicitly retrieve columns (i.e. fields/subitems) other than the first in the
  2521.     // case of Explorer's Icon/List view modes behave the same as fetching the first column (i.e. Col3
  2522.     // would retrieve the same text as specifying Col1 or not having the Col option at all).
  2523.     // Obsolete because not always true: Testing shows that a ListView always has a header control
  2524.     // (at least on XP), even if you can't see it (such as when the view is Icon/Tile or when -Hdr has
  2525.     // been specified in the options).
  2526.     HWND header_control;
  2527.     LRESULT col_count = -1;  // Fix for v1.0.37.01: Use -1 to indicate "undetermined col count".
  2528.     if (SendMessageTimeout(aHwnd, LVM_GETHEADER, 0, 0, SMTO_ABORTIFHUNG, 2000, (PDWORD_PTR)&header_control)
  2529.         && header_control) // Relies on short-circuit boolean order.
  2530.         SendMessageTimeout(header_control, HDM_GETITEMCOUNT, 0, 0, SMTO_ABORTIFHUNG, 2000, (PDWORD_PTR)&col_count);
  2531.         // Return value is not checked because if it fails, col_count is left at its default of -1 set above.
  2532.         // In fact, if any of the above conditions made it impossible to determine col_count, col_count stays
  2533.         // at -1 to indicate "undetermined".
  2534.  
  2535.     // PARSE OPTIONS (a simple vs. strict method is used to reduce code size)
  2536.     bool get_count = strcasestr(aOptions, "Count");
  2537.     bool include_selected_only = strcasestr(aOptions, "Selected"); // Explicit "ed" to reserve "Select" for possible future use.
  2538.     bool include_focused_only = strcasestr(aOptions, "Focused");  // Same.
  2539.     char *col_option = strcasestr(aOptions, "Col"); // Also used for mode "Count Col"
  2540.     int requested_col = col_option ? ATOI(col_option + 3) - 1 : -1;
  2541.     // If the above yields a negative col number for any reason, it's ok because below will just ignore it.
  2542.     if (col_count > -1 && requested_col > -1 && requested_col >= col_count) // Specified column does not exist.
  2543.         return OK;  // Let ErrorLevel tell the story.
  2544.  
  2545.     // IF THE "COUNT" OPTION IS PRESENT, FULLY HANDLE THAT AND RETURN
  2546.     if (get_count)
  2547.     {
  2548.         int result; // Must be signed to support writing a col count of -1 to aOutputVar.
  2549.         if (include_focused_only) // Listed first so that it takes precedence over include_selected_only.
  2550.         {
  2551.             if (!SendMessageTimeout(aHwnd, LVM_GETNEXTITEM, -1, LVNI_FOCUSED, SMTO_ABORTIFHUNG, 2000, (PDWORD_PTR)&result)) // Timed out or failed.
  2552.                 return OK;  // Let ErrorLevel tell the story.
  2553.             ++result; // i.e. Set it to 0 if not found, or the 1-based row-number otherwise.
  2554.         }
  2555.         else if (include_selected_only)
  2556.         {
  2557.             if (!SendMessageTimeout(aHwnd, LVM_GETSELECTEDCOUNT, 0, 0, SMTO_ABORTIFHUNG, 2000, (PDWORD_PTR)&result)) // Timed out or failed.
  2558.                 return OK;  // Let ErrorLevel tell the story.
  2559.         }
  2560.         else if (col_option) // "Count Col" returns the number of columns.
  2561.             result = (int)col_count;
  2562.         else // Total row count.
  2563.             result = (int)row_count;
  2564.         g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  2565.         return aOutputVar.Assign(result);
  2566.     }
  2567.  
  2568.     // FINAL CHECKS
  2569.     if (row_count < 1 || !col_count) // But don't return when col_count == -1 (i.e. always make the attempt when col count is undetermined).
  2570.         return g_ErrorLevel->Assign(ERRORLEVEL_NONE);  // No text in the control, so indicate success.
  2571.  
  2572.     // ALLOCATE INTERPROCESS MEMORY FOR TEXT RETRIEVAL
  2573.     HANDLE handle;
  2574.     LPVOID p_remote_lvi; // Not of type LPLVITEM to help catch bugs where p_remote_lvi->member is wrongly accessed here in our process.
  2575.     if (   !(p_remote_lvi = AllocInterProcMem(handle, LV_REMOTE_BUF_SIZE + sizeof(LVITEM), aHwnd))   ) // Allocate the right type of memory (depending on OS type). Allocate both the LVITEM struct and its internal string buffer in one go because MyVirtualAllocEx() is probably a high overhead call.
  2576.         return OK;  // Let ErrorLevel tell the story.
  2577.     bool is_win9x = g_os.IsWin9x(); // Resolve once for possible slight perf./code size benefit.
  2578.  
  2579.     // PREPARE LVI STRUCT MEMBERS FOR TEXT RETRIEVAL
  2580.     LVITEM lvi_for_nt; // Only used for NT/2k/XP method.
  2581.     LVITEM &local_lvi = is_win9x ? *(LPLVITEM)p_remote_lvi : lvi_for_nt; // Local is the same as remote for Win9x.
  2582.     // Subtract 1 because of that nagging doubt about size vs. length. Some MSDN examples subtract one,
  2583.     // such as TabCtrl_GetItem()'s cchTextMax:
  2584.     local_lvi.cchTextMax = LV_REMOTE_BUF_SIZE - 1; // Note that LVM_GETITEM doesn't update this member to reflect the new length.
  2585.     local_lvi.pszText = (char *)p_remote_lvi + sizeof(LVITEM); // The next buffer is the memory area adjacent to, but after the struct.
  2586.  
  2587.     LRESULT i, next, length, total_length;
  2588.     bool is_selective = include_focused_only || include_selected_only;
  2589.     bool single_col_mode = (requested_col > -1 || col_count == -1); // Get only one column in these cases.
  2590.  
  2591.     // ESTIMATE THE AMOUNT OF MEMORY NEEDED TO STORE ALL THE TEXT
  2592.     // It's important to note that a ListView might legitimately have a collection of rows whose
  2593.     // fields are all empty.  Since it is difficult to know whether the control is truly owner-drawn
  2594.     // (checking its style might not be enough?), there is no way to distinguish this condition
  2595.     // from one where the control's text can't be retrieved due to being owner-drawn.  In any case,
  2596.     // this all-empty-field behavior simplifies the code and will be documented in the help file.
  2597.     for (i = 0, next = -1, total_length = 0; i < row_count; ++i) // For each row:
  2598.     {
  2599.         if (is_selective)
  2600.         {
  2601.             // Fix for v1.0.37.01: Prevent an infinite loop that might occur if the target control no longer
  2602.             // exists (perhaps having been closed in the middle of the operation) or is permanently hung.
  2603.             // If GetLastError() were to return zero after the below, it would mean the function timed out.
  2604.             // However, rather than checking and retrying, it seems better to abort the operation because:
  2605.             // 1) Timeout should be quite rare.
  2606.             // 2) Reduces code size.
  2607.             // 3) Having a retry really should be accompanied by SLEEP_WITHOUT_INTERRUPTION because all this
  2608.             //    time our thread would not pumping messages (and worse, if the keyboard/mouse hooks are installed,
  2609.             //    mouse/key lag would occur).
  2610.             if (!SendMessageTimeout(aHwnd, LVM_GETNEXTITEM, next, include_focused_only ? LVNI_FOCUSED : LVNI_SELECTED
  2611.                 , SMTO_ABORTIFHUNG, 2000, (PDWORD_PTR)&next) // Timed out or failed.
  2612.                 || next == -1) // No next item.  Relies on short-circuit boolean order.
  2613.                 break; // End of estimation phase (if estimate is too small, the text retrieval below will truncate it).
  2614.         }
  2615.         else
  2616.             next = i;
  2617.         for (local_lvi.iSubItem = (requested_col > -1) ? requested_col : 0 // iSubItem is which field to fetch. If it's zero, the item vs. subitem will be fetched.
  2618.             ; col_count == -1 || local_lvi.iSubItem < col_count // If column count is undetermined (-1), always make the attempt.
  2619.             ; ++local_lvi.iSubItem) // For each column:
  2620.         {
  2621.             if ((is_win9x || WriteProcessMemory(handle, p_remote_lvi, &local_lvi, sizeof(LVITEM), NULL)) // Relies on short-circuit boolean order.
  2622.                 && SendMessageTimeout(aHwnd, LVM_GETITEMTEXT, next, (LPARAM)p_remote_lvi, SMTO_ABORTIFHUNG, 2000, (PDWORD_PTR)&length))
  2623.                 total_length += length;
  2624.             //else timed out or failed, don't include the length in the estimate.  Instead, the
  2625.             // text-fetching routine below will ensure the text doesn't overflow the var capacity.
  2626.             if (single_col_mode)
  2627.                 break;
  2628.         }
  2629.     }
  2630.     // Add to total_length enough room for one linefeed per row, and one tab after each column
  2631.     // except the last (formula verified correct, though it's inflated by 1 for safety). "i" contains the
  2632.     // actual number of rows that will be transcribed, which might be less than row_count if is_selective==true.
  2633.     total_length += i * (single_col_mode ? 1 : col_count);
  2634.  
  2635.     // SET UP THE OUTPUT VARIABLE, ENLARGING IT IF NECESSARY
  2636.     // If the aOutputVar is of type VAR_CLIPBOARD, this call will set up the clipboard for writing:
  2637.     aOutputVar.Assign(NULL, (VarSizeType)total_length, true, false); // Since failure is extremely rare, continue onward using the available capacity.
  2638.     char *contents = aOutputVar.Contents();
  2639.     LRESULT capacity = (int)aOutputVar.Capacity(); // LRESULT avoids signed vs. unsigned compiler warnings.
  2640.     if (capacity > 0) // For maintainability, avoid going negative.
  2641.         --capacity; // Adjust to exclude the zero terminator, which simplifies things below.
  2642.  
  2643.     // RETRIEVE THE TEXT FROM THE REMOTE LISTVIEW
  2644.     // Start total_length at zero in case actual size is greater than estimate, in which case only a partial set of text along with its '\t' and '\n' chars will be written.
  2645.     for (i = 0, next = -1, total_length = 0; i < row_count; ++i) // For each row:
  2646.     {
  2647.         if (is_selective)
  2648.         {
  2649.             // Fix for v1.0.37.01: Prevent an infinite loop (for details, see comments in the estimation phase above).
  2650.             if (!SendMessageTimeout(aHwnd, LVM_GETNEXTITEM, next, include_focused_only ? LVNI_FOCUSED : LVNI_SELECTED
  2651.                 , SMTO_ABORTIFHUNG, 2000, (PDWORD_PTR)&next) // Timed out or failed.
  2652.                 || next == -1) // No next item.
  2653.                 break; // See comment above for why unconditional break vs. continue.
  2654.         }
  2655.         else // Retrieve every row, so the "next" row becomes the "i" index.
  2656.             next = i;
  2657.         // Insert a linefeed before each row except the first:
  2658.         if (i && total_length < capacity) // If we're at capacity, it will exit the loops when the next field is read.
  2659.         {
  2660.             *contents++ = '\n';
  2661.             ++total_length;
  2662.         }
  2663.  
  2664.         // iSubItem is which field to fetch. If it's zero, the item vs. subitem will be fetched:
  2665.         for (local_lvi.iSubItem = (requested_col > -1) ? requested_col : 0
  2666.             ; col_count == -1 || local_lvi.iSubItem < col_count // If column count is undetermined (-1), always make the attempt.
  2667.             ; ++local_lvi.iSubItem) // For each column:
  2668.         {
  2669.             // Insert a tab before each column except the first and except when in single-column mode:
  2670.             if (!single_col_mode && local_lvi.iSubItem && total_length < capacity)  // If we're at capacity, it will exit the loops when the next field is read.
  2671.             {
  2672.                 *contents++ = '\t';
  2673.                 ++total_length;
  2674.             }
  2675.  
  2676.             if (!(is_win9x || WriteProcessMemory(handle, p_remote_lvi, &local_lvi, sizeof(LVITEM), NULL)) // Relies on short-circuit boolean order.
  2677.                 || !SendMessageTimeout(aHwnd, LVM_GETITEMTEXT, next, (LPARAM)p_remote_lvi, SMTO_ABORTIFHUNG, 2000, (PDWORD_PTR)&length))
  2678.                 continue; // Timed out or failed. It seems more useful to continue getting text rather than aborting the operation.
  2679.  
  2680.             // Otherwise, the message was successfully sent.
  2681.             if (length > 0)
  2682.             {
  2683.                 if (total_length + length > capacity)
  2684.                     goto break_both; // "goto" for simplicity and code size reduction.
  2685.                 // Otherwise:
  2686.                 // READ THE TEXT FROM THE REMOTE PROCESS
  2687.                 // Although MSDN has the following comment about LVM_GETITEM, it is not present for
  2688.                 // LVM_GETITEMTEXT. Therefore, to improve performance (by avoiding a second call to
  2689.                 // ReadProcessMemory) and to reduce code size, we'll take them at their word until
  2690.                 // proven otherwise.  Here is the MSDN comment about LVM_GETITEM: "Applications
  2691.                 // should not assume that the text will necessarily be placed in the specified
  2692.                 // buffer. The control may instead change the pszText member of the structure
  2693.                 // to point to the new text, rather than place it in the buffer."
  2694.                 if (is_win9x)
  2695.                 {
  2696.                     memcpy(contents, local_lvi.pszText, length); // Usually benches a little faster than strcpy().
  2697.                     contents += length; // Point it to the position where the next char will be written.
  2698.                     total_length += length; // Recalculate length in case its different than the estimate (for any reason).
  2699.                 }
  2700.                 else
  2701.                 {
  2702.                     if (ReadProcessMemory(handle, local_lvi.pszText, contents, length, NULL)) // local_lvi.pszText == p_remote_lvi->pszText
  2703.                     {
  2704.                         contents += length; // Point it to the position where the next char will be written.
  2705.                         total_length += length; // Recalculate length in case its different than the estimate (for any reason).
  2706.                     }
  2707.                     //else it failed; but even so, continue on to put in a tab (if called for).
  2708.                 }
  2709.             }
  2710.             //else length is zero; but even so, continue on to put in a tab (if called for).
  2711.             if (single_col_mode)
  2712.                 break;
  2713.         } // for() each column
  2714.     } // for() each row
  2715.  
  2716. break_both:
  2717.     if (contents) // Might be NULL if Assign() failed and thus var has zero capacity.
  2718.         *contents = '\0'; // Final termination.  Above has reserved room for for this one byte.
  2719.  
  2720.     // CLEAN UP
  2721.     FreeInterProcMem(handle, p_remote_lvi);
  2722.     aOutputVar.Close(); // In case it's the clipboard.
  2723.     aOutputVar.Length() = (VarSizeType)total_length; // Update to actual vs. estimated length.
  2724.     return g_ErrorLevel->Assign(ERRORLEVEL_NONE);  // Indicate success.
  2725. }
  2726.  
  2727.  
  2728.  
  2729. ResultType Line::StatusBarGetText(char *aPart, char *aTitle, char *aText
  2730.     , char *aExcludeTitle, char *aExcludeText)
  2731. {
  2732.     // Note: ErrorLevel is handled by StatusBarUtil(), below.
  2733.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  2734.     HWND control_window = target_window ? ControlExist(target_window, "msctls_statusbar321") : NULL;
  2735.     // Call this even if control_window is NULL because in that case, it will set the output var to
  2736.     // be blank for us:
  2737.     return StatusBarUtil(OUTPUT_VAR, control_window, ATOI(aPart)); // It will handle any zero part# for us.
  2738. }
  2739.  
  2740.  
  2741.  
  2742. ResultType Line::StatusBarWait(char *aTextToWaitFor, char *aSeconds, char *aPart, char *aTitle, char *aText
  2743.     , char *aInterval, char *aExcludeTitle, char *aExcludeText)
  2744. // Since other script threads can interrupt this command while it's running, it's important that
  2745. // this command not refer to sArgDeref[] and sArgVar[] anytime after an interruption becomes possible.
  2746. // This is because an interrupting thread usually changes the values to something inappropriate for this thread.
  2747. {
  2748.     // Note: ErrorLevel is handled by StatusBarUtil(), below.
  2749.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  2750.     // Make a copy of any memory areas that are volatile (due to Deref buf being overwritten
  2751.     // if a new hotkey subroutine is launched while we are waiting) but whose contents we
  2752.     // need to refer to while we are waiting:
  2753.     char text_to_wait_for[4096];
  2754.     strlcpy(text_to_wait_for, aTextToWaitFor, sizeof(text_to_wait_for));
  2755.     HWND control_window = target_window ? ControlExist(target_window, "msctls_statusbar321") : NULL;
  2756.     return StatusBarUtil(NULL, control_window, ATOI(aPart) // It will handle a NULL control_window or zero part# for us.
  2757.         , text_to_wait_for, *aSeconds ? (int)(ATOF(aSeconds)*1000) : -1 // Blank->indefinite.  0 means 500ms.
  2758.         , ATOI(aInterval));
  2759. }
  2760.  
  2761.  
  2762.  
  2763. ResultType Line::ScriptPostSendMessage(bool aUseSend)
  2764. // Arg list:
  2765. // sArgDeref[0]: Msg number
  2766. // sArgDeref[1]: wParam
  2767. // sArgDeref[2]: lParam
  2768. // sArgDeref[3]: Control
  2769. // sArgDeref[4]: WinTitle
  2770. // sArgDeref[5]: WinText
  2771. // sArgDeref[6]: ExcludeTitle
  2772. // sArgDeref[7]: ExcludeText
  2773. {
  2774.     HWND target_window, control_window;
  2775.     if (   !(target_window = DetermineTargetWindow(sArgDeref[4], sArgDeref[5], sArgDeref[6], sArgDeref[7]))
  2776.         || !(control_window = *sArgDeref[3] ? ControlExist(target_window, sArgDeref[3]) : target_window)   ) // Relies on short-circuit boolean order.
  2777.         return g_ErrorLevel->Assign(aUseSend ? "FAIL" : ERRORLEVEL_ERROR); // Need a special value to distinguish this from numeric reply-values.
  2778.  
  2779.     // UPDATE: Note that ATOU(), in both past and current versions, supports negative numbers too.
  2780.     // For example, ATOU("-1") has always produced 0xFFFFFFFF.
  2781.     // Use ATOU() to support unsigned (i.e. UINT, LPARAM, and WPARAM are all 32-bit unsigned values).
  2782.     // ATOU() also supports hex strings in the script, such as 0xFF, which is why it's commonly
  2783.     // used in functions such as this.  v1.0.40.05: Support the passing of a literal (quoted) string
  2784.     // by checking whether the original/raw arg's first character is '"'.  The avoids the need to
  2785.     // put the string into a variable and then pass something like &MyVar.
  2786.     UINT msg = ATOU(sArgDeref[0]);
  2787.     WPARAM wparam = (mArgc > 1 && mArg[1].text[0] == '"') ? (WPARAM)sArgDeref[1] : ATOU(sArgDeref[1]);
  2788.     LPARAM lparam = (mArgc > 2 && mArg[2].text[0] == '"') ? (LPARAM)sArgDeref[2] : ATOU(sArgDeref[2]);
  2789.  
  2790.     if (aUseSend)
  2791.     {
  2792.         DWORD dwResult;
  2793.         // Timeout increased from 2000 to 5000 in v1.0.27:
  2794.         if (!SendMessageTimeout(control_window, msg, wparam, lparam, SMTO_ABORTIFHUNG, 5000, &dwResult))
  2795.             return g_ErrorLevel->Assign("FAIL"); // Need a special value to distinguish this from numeric reply-values.
  2796.         g_ErrorLevel->Assign(dwResult); // UINT seems best most of the time?
  2797.     }
  2798.     else // Post vs. Send
  2799.     {
  2800.         if (!PostMessage(control_window, msg, wparam, lparam))
  2801.             return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  2802.         g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  2803.     }
  2804.  
  2805.     // v1.0.43.06: If either wParam or lParam contained the address of a variable, update the mLength
  2806.     // member in case the receiver of the message wrote something to the buffer.  This is similar to the
  2807.     // way "Str" parameters work in DllCall.
  2808.     for (int i = 1; i < 3; ++i) // Two iterations: wParam and lParam.
  2809.     {
  2810.         if (mArgc > i) // The arg exists.
  2811.         {
  2812.             ArgStruct &this_arg = mArg[i];
  2813.             if (this_arg.text[0] == '&' && this_arg.deref && !this_arg.deref->is_function) // Must start with '&', so things like 5+&MyVar aren't supported.
  2814.                 this_arg.deref->var->SetLengthFromContents();
  2815.         }
  2816.     }
  2817.  
  2818.     // By design (since this is a power user feature), no ControlDelay is done here.
  2819.     return OK;
  2820. }
  2821.  
  2822.  
  2823.  
  2824. ResultType Line::ScriptProcess(char *aCmd, char *aProcess, char *aParam3)
  2825. {
  2826.     ProcessCmds process_cmd = ConvertProcessCmd(aCmd);
  2827.     // Runtime error is rare since it is caught at load-time unless it's in a var. ref.
  2828.     if (process_cmd == PROCESS_CMD_INVALID)
  2829.         return LineError(ERR_PARAM1_INVALID ERR_ABORT, FAIL, aCmd);
  2830.  
  2831.     HANDLE hProcess;
  2832.     DWORD pid, priority;
  2833.     BOOL result;
  2834.  
  2835.     switch (process_cmd)
  2836.     {
  2837.     case PROCESS_CMD_EXIST:
  2838.         return g_ErrorLevel->Assign(*aProcess ? ProcessExist(aProcess) : GetCurrentProcessId()); // The discovered PID or zero if none.
  2839.  
  2840.     case PROCESS_CMD_CLOSE:
  2841.         if (pid = ProcessExist(aProcess))  // Assign
  2842.         {
  2843.             if (hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid))
  2844.             {
  2845.                 result = TerminateProcess(hProcess, 0);
  2846.                 CloseHandle(hProcess);
  2847.                 return g_ErrorLevel->Assign(result ? pid : 0); // Indicate success or failure.
  2848.             }
  2849.         }
  2850.         // Since above didn't return, yield a PID of 0 to indicate failure.
  2851.         return g_ErrorLevel->Assign("0");
  2852.  
  2853.     case PROCESS_CMD_PRIORITY:
  2854.         switch (toupper(*aParam3))
  2855.         {
  2856.         case 'L': priority = IDLE_PRIORITY_CLASS; break;
  2857.         case 'B': priority = BELOW_NORMAL_PRIORITY_CLASS; break;
  2858.         case 'N': priority = NORMAL_PRIORITY_CLASS; break;
  2859.         case 'A': priority = ABOVE_NORMAL_PRIORITY_CLASS; break;
  2860.         case 'H': priority = HIGH_PRIORITY_CLASS; break;
  2861.         case 'R': priority = REALTIME_PRIORITY_CLASS; break;
  2862.         default:
  2863.             return g_ErrorLevel->Assign("0");  // 0 indicates failure in this case (i.e. a PID of zero).
  2864.         }
  2865.         if (pid = *aProcess ? ProcessExist(aProcess) : GetCurrentProcessId())  // Assign
  2866.         {
  2867.             if (hProcess = OpenProcess(PROCESS_SET_INFORMATION, FALSE, pid)) // Assign
  2868.             {
  2869.                 // If OS doesn't support "above/below normal", seems best to default to normal rather than high/low,
  2870.                 // since "above/below normal" aren't that dramatically different from normal:
  2871.                 if (!g_os.IsWin2000orLater() && (priority == BELOW_NORMAL_PRIORITY_CLASS || priority == ABOVE_NORMAL_PRIORITY_CLASS))
  2872.                     priority = NORMAL_PRIORITY_CLASS;
  2873.                 result = SetPriorityClass(hProcess, priority);
  2874.                 CloseHandle(hProcess);
  2875.                 return g_ErrorLevel->Assign(result ? pid : 0); // Indicate success or failure.
  2876.             }
  2877.         }
  2878.         // Otherwise, return a PID of 0 to indicate failure.
  2879.         return g_ErrorLevel->Assign("0");
  2880.  
  2881.     case PROCESS_CMD_WAIT:
  2882.     case PROCESS_CMD_WAITCLOSE:
  2883.     {
  2884.         // This section is similar to that used for WINWAIT and RUNWAIT:
  2885.         bool wait_indefinitely;
  2886.         int sleep_duration;
  2887.         DWORD start_time;
  2888.         if (*aParam3) // the param containing the timeout value isn't blank.
  2889.         {
  2890.             wait_indefinitely = false;
  2891.             sleep_duration = (int)(ATOF(aParam3) * 1000); // Can be zero.
  2892.             start_time = GetTickCount();
  2893.         }
  2894.         else
  2895.         {
  2896.             wait_indefinitely = true;
  2897.             sleep_duration = 0; // Just to catch any bugs.
  2898.         }
  2899.         for (;;)
  2900.         { // Always do the first iteration so that at least one check is done.
  2901.             pid = ProcessExist(aProcess);
  2902.             if (process_cmd == PROCESS_CMD_WAIT)
  2903.             {
  2904.                 if (pid)
  2905.                     return g_ErrorLevel->Assign(pid);
  2906.             }
  2907.             else // PROCESS_CMD_WAITCLOSE
  2908.             {
  2909.                 // Since PID cannot always be determined (i.e. if process never existed, there was
  2910.                 // no need to wait for it to close), for consistency, return 0 on success.
  2911.                 if (!pid)
  2912.                     return g_ErrorLevel->Assign("0");
  2913.             }
  2914.             // Must cast to int or any negative result will be lost due to DWORD type:
  2915.             if (wait_indefinitely || (int)(sleep_duration - (GetTickCount() - start_time)) > SLEEP_INTERVAL_HALF)
  2916.                 MsgSleep(100);  // For performance reasons, don't check as often as the WinWait family does.
  2917.             else // Done waiting.
  2918.                 return g_ErrorLevel->Assign(pid);
  2919.                 // Above assigns 0 if "Process Wait" times out; or the PID of the process that still exists
  2920.                 // if "Process WaitClose" times out.
  2921.         } // for()
  2922.     } // case
  2923.     } // switch()
  2924.  
  2925.     return FAIL;  // Should never be executed; just here to catch bugs.
  2926. }
  2927.  
  2928.  
  2929.  
  2930. ResultType WinSetRegion(HWND aWnd, char *aPoints)
  2931. // Caller has initialized g_ErrorLevel to ERRORLEVEL_ERROR for us.
  2932. {
  2933.     if (!*aPoints) // Attempt to restore the window's normal/correct region.
  2934.     {
  2935.         // Fix for v1.0.31.07: The old method used the following, but apparently it's not the correct
  2936.         // way to restore a window's proper/normal region because when such a window is later maximized,
  2937.         // it retains its incorrect/smaller region:
  2938.         //if (GetWindowRect(aWnd, &rect))
  2939.         //{
  2940.         //    // Adjust the rect to keep the same size but have its upper-left corner at 0,0:
  2941.         //    rect.right -= rect.left;
  2942.         //    rect.bottom -= rect.top;
  2943.         //    rect.left = 0;
  2944.         //    rect.top = 0;
  2945.         //    if (hrgn = CreateRectRgnIndirect(&rect)) // Assign
  2946.         //    {
  2947.         //        // Presumably, the system deletes the former region when upon a successful call to SetWindowRgn().
  2948.         //        if (SetWindowRgn(aWnd, hrgn, TRUE))
  2949.         //            return g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  2950.         //        // Otherwise, get rid of it since it didn't take effect:
  2951.         //        DeleteObject(hrgn);
  2952.         //    }
  2953.         //}
  2954.         //// Since above didn't return:
  2955.         //return OK; // Let ErrorLevel tell the story.
  2956.  
  2957.         // It's undocumented by MSDN, but apparently setting the Window's region to NULL restores it
  2958.         // to proper working order:
  2959.         if (SetWindowRgn(aWnd, NULL, TRUE))
  2960.             return g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  2961.         return OK; // Let ErrorLevel tell the story.
  2962.     }
  2963.  
  2964.     #define MAX_REGION_POINTS 2000  // 2000 requires 16 KB of stack space.
  2965.     POINT pt[MAX_REGION_POINTS];
  2966.     int pt_count;
  2967.     char *cp;
  2968.  
  2969.     // Set defaults prior to parsing options in case any options are absent:
  2970.     int width = COORD_UNSPECIFIED;
  2971.     int height = COORD_UNSPECIFIED;
  2972.     int rr_width = COORD_UNSPECIFIED; // These two are for the rounded-rectangle method.
  2973.     int rr_height = COORD_UNSPECIFIED;
  2974.     bool use_ellipse = false;
  2975.  
  2976.     int fill_mode = ALTERNATE;
  2977.     // Concerning polygon regions: ALTERNATE is used by default (somewhat arbitrarily, but it seems to be the
  2978.     // more typical default).
  2979.     // MSDN: "In general, the modes [ALTERNATE vs. WINDING] differ only in cases where a complex,
  2980.     // overlapping polygon must be filled (for example, a five-sided polygon that forms a five-pointed
  2981.     // star with a pentagon in the center). In such cases, ALTERNATE mode fills every other enclosed
  2982.     // region within the polygon (that is, the points of the star), but WINDING mode fills all regions
  2983.     // (that is, the points and the pentagon)."
  2984.  
  2985.     for (pt_count = 0, cp = aPoints; *(cp = omit_leading_whitespace(cp));)
  2986.     {
  2987.         // To allow the MAX to be increased in the future with less chance of breaking existing scripts, consider this an error.
  2988.         if (pt_count >= MAX_REGION_POINTS)
  2989.             return OK; // Let ErrorLevel tell the story.
  2990.  
  2991.         if (isdigit(*cp) || *cp == '-' || *cp == '+') // v1.0.38.02: Recognize leading minus/plus sign so that the X-coord is just as tolerant as the Y.
  2992.         {
  2993.             // Assume it's a pair of X/Y coordinates.  It's done this way rather than using X and Y
  2994.             // as option letters because:
  2995.             // 1) The script is more readable when there are multiple coordinates (for polygon).
  2996.             // 2) It enforces the fact that each X must have a Y and that X must always come before Y
  2997.             //    (which simplifies and reduces the size of the code).
  2998.             pt[pt_count].x = ATOI(cp);
  2999.             // For the delimiter, dash is more readable than pipe, even though it overlaps with "minus sign".
  3000.             // "x" is not used to avoid detecting "x" inside hex numbers.
  3001.             #define REGION_DELIMITER '-'
  3002.             if (   !(cp = strchr(cp + 1, REGION_DELIMITER))   ) // v1.0.38.02: cp + 1 to omit any leading minus sign.
  3003.                 return OK; // Let ErrorLevel tell the story.
  3004.             pt[pt_count].y = ATOI(++cp);  // Increment cp by only 1 to support negative Y-coord.
  3005.             ++pt_count; // Move on to the next element of the pt array.
  3006.         }
  3007.         else
  3008.         {
  3009.             ++cp;
  3010.             switch(toupper(cp[-1]))
  3011.             {
  3012.             case 'E':
  3013.                 use_ellipse = true;
  3014.                 break;
  3015.             case 'R':
  3016.                 if (!*cp || *cp == ' ') // Use 30x30 default.
  3017.                 {
  3018.                     rr_width = 30;
  3019.                     rr_height = 30;
  3020.                 }
  3021.                 else
  3022.                 {
  3023.                     rr_width = ATOI(cp);
  3024.                     if (cp = strchr(cp, REGION_DELIMITER)) // Assign
  3025.                         rr_height = ATOI(++cp);
  3026.                     else // Avoid problems with going beyond the end of the string.
  3027.                         return OK; // Let ErrorLevel tell the story.
  3028.                 }
  3029.                 break;
  3030.             case 'W':
  3031.                 if (!strnicmp(cp, "ind", 3)) // [W]ind.
  3032.                     fill_mode = WINDING;
  3033.                 else
  3034.                     width = ATOI(cp);
  3035.                 break;
  3036.             case 'H':
  3037.                 height = ATOI(cp);
  3038.                 break;
  3039.             default: // For simplicity and to reserve other letters for future use, unknown options result in failure.
  3040.                 return OK; // Let ErrorLevel tell the story.
  3041.             } // switch()
  3042.         } // else
  3043.  
  3044.         if (   !(cp = strchr(cp, ' '))   ) // No more items.
  3045.             break;
  3046.     }
  3047.  
  3048.     if (!pt_count)
  3049.         return OK; // Let ErrorLevel tell the story.
  3050.  
  3051.     bool width_and_height_were_both_specified = !(width == COORD_UNSPECIFIED || height == COORD_UNSPECIFIED);
  3052.     if (width_and_height_were_both_specified)
  3053.     {
  3054.         width += pt[0].x;   // Make width become the right side of the rect.
  3055.         height += pt[0].y;  // Make height become the bottom.
  3056.     }
  3057.  
  3058.     HRGN hrgn;
  3059.     if (use_ellipse) // Ellipse.
  3060.     {
  3061.         if (!width_and_height_were_both_specified || !(hrgn = CreateEllipticRgn(pt[0].x, pt[0].y, width, height)))
  3062.             return OK; // Let ErrorLevel tell the story.
  3063.     }
  3064.     else if (rr_width != COORD_UNSPECIFIED) // Rounded rectangle.
  3065.     {
  3066.         if (!width_and_height_were_both_specified || !(hrgn = CreateRoundRectRgn(pt[0].x, pt[0].y, width, height, rr_width, rr_height)))
  3067.             return OK; // Let ErrorLevel tell the story.
  3068.     }
  3069.     else if (width_and_height_were_both_specified) // Rectangle.
  3070.     {
  3071.         if (!(hrgn = CreateRectRgn(pt[0].x, pt[0].y, width, height)))
  3072.             return OK; // Let ErrorLevel tell the story.
  3073.     }
  3074.     else // Polygon
  3075.     {
  3076.         if (   !(hrgn = CreatePolygonRgn(pt, pt_count, fill_mode))   )
  3077.             return OK;
  3078.     }
  3079.  
  3080.     // Since above didn't return, hrgn is now a non-NULL region ready to be assigned to the window.
  3081.  
  3082.     // Presumably, the system deletes the window's former region upon a successful call to SetWindowRgn():
  3083.     if (!SetWindowRgn(aWnd, hrgn, TRUE))
  3084.     {
  3085.         DeleteObject(hrgn);
  3086.         return OK; // Let ErrorLevel tell the story.
  3087.     }
  3088.     //else don't delete hrgn since the system has taken ownership of it.
  3089.  
  3090.     // Since above didn't return, indicate success.
  3091.     return g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  3092. }
  3093.  
  3094.  
  3095.                         
  3096. ResultType Line::WinSet(char *aAttrib, char *aValue, char *aTitle, char *aText
  3097.     , char *aExcludeTitle, char *aExcludeText)
  3098. {
  3099.     WinSetAttributes attrib = ConvertWinSetAttribute(aAttrib);
  3100.     if (attrib == WINSET_INVALID)
  3101.         return LineError(ERR_PARAM1_INVALID, FAIL, aAttrib);
  3102.  
  3103.     // Set default ErrorLevel for any commands that change ErrorLevel.
  3104.     // The default should be ERRORLEVEL_ERROR so that that value will be returned
  3105.     // by default when the target window doesn't exist:
  3106.     if (attrib == WINSET_STYLE || attrib == WINSET_EXSTYLE || attrib == WINSET_REGION)
  3107.         g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  3108.  
  3109.     // Since this is a macro, avoid repeating it for every case of the switch():
  3110.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  3111.     if (!target_window)
  3112.         return OK; // Let ErrorLevel (for commands that set it above) tell the story.
  3113.  
  3114.     int value;
  3115.     DWORD exstyle;
  3116.  
  3117.     switch (attrib)
  3118.     {
  3119.     case WINSET_ALWAYSONTOP:
  3120.     {
  3121.         if (   !(exstyle = GetWindowLong(target_window, GWL_EXSTYLE))   )
  3122.             return OK;
  3123.         HWND topmost_or_not;
  3124.         switch(ConvertOnOffToggle(aValue))
  3125.         {
  3126.         case TOGGLED_ON: topmost_or_not = HWND_TOPMOST; break;
  3127.         case TOGGLED_OFF: topmost_or_not = HWND_NOTOPMOST; break;
  3128.         case NEUTRAL: // parameter was blank, so it defaults to TOGGLE.
  3129.         case TOGGLE: topmost_or_not = (exstyle & WS_EX_TOPMOST) ? HWND_NOTOPMOST : HWND_TOPMOST; break;
  3130.         default: return OK;
  3131.         }
  3132.         // SetWindowLong() didn't seem to work, at least not on some windows.  But this does.
  3133.         // As of v1.0.25.14, SWP_NOACTIVATE is also specified, though its absence does not actually
  3134.         // seem to activate the window, at least on XP (perhaps due to anti-focus-stealing measure
  3135.         // in Win98/2000 and beyond).  Or perhaps its something to do with the presence of
  3136.         // topmost_or_not (HWND_TOPMOST/HWND_NOTOPMOST), which might always avoid activating the
  3137.         // window.
  3138.         SetWindowPos(target_window, topmost_or_not, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
  3139.         break;
  3140.     }
  3141.  
  3142.     // Note that WINSET_TOP is not offered as an option since testing reveals it has no effect on
  3143.     // top level (parent) windows, perhaps due to the anti focus-stealing measures in the OS.
  3144.     case WINSET_BOTTOM:
  3145.         // Note: SWP_NOACTIVATE must be specified otherwise the target window often/always fails to go
  3146.         // to the bottom:
  3147.         SetWindowPos(target_window, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
  3148.         break;
  3149.     case WINSET_TOP:
  3150.         // Note: SWP_NOACTIVATE must be specified otherwise the target window often/always fails to go
  3151.         // to the bottom:
  3152.         SetWindowPos(target_window, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
  3153.         break;
  3154.  
  3155.     case WINSET_TRANSPARENT:
  3156.     case WINSET_TRANSCOLOR:
  3157.     {
  3158.         // IMPORTANT (when considering future enhancements to these commands): Unlike
  3159.         // SetLayeredWindowAttributes(), which works on Windows 2000, GetLayeredWindowAttributes()
  3160.         // is supported only on XP or later.
  3161.  
  3162.         // It appears that turning on WS_EX_LAYERED in an attempt to retrieve the window's
  3163.         // former transparency setting does not work.  The OS probably does not store the
  3164.         // former transparency level (i.e. it forgets it the moment the WS_EX_LAYERED exstyle
  3165.         // is turned off).  This is true even if the following are done after the SetWindowLong():
  3166.         //MySetLayeredWindowAttributes(target_window, 0, 0, 0)
  3167.         // or:
  3168.         //if (MyGetLayeredWindowAttributes(target_window, &color, &alpha, &flags))
  3169.         //    MySetLayeredWindowAttributes(target_window, color, alpha, flags);
  3170.         // The above is why there is currently no "on" or "toggle" sub-command, just "Off".
  3171.  
  3172.         // Must fetch the below at runtime, otherwise the program can't even be launched on Win9x/NT.
  3173.         // Also, since the color of an HBRUSH can't be easily determined (since it can be a pattern and
  3174.         // since there seem to be no easy API calls to discover the colors of pixels in an HBRUSH),
  3175.         // the following is not yet implemented: Use window's own class background color (via
  3176.         // GetClassLong) if aValue is entirely blank.
  3177.         typedef BOOL (WINAPI *MySetLayeredWindowAttributesType)(HWND, COLORREF, BYTE, DWORD);
  3178.         static MySetLayeredWindowAttributesType MySetLayeredWindowAttributes = (MySetLayeredWindowAttributesType)
  3179.             GetProcAddress(GetModuleHandle("user32"), "SetLayeredWindowAttributes");
  3180.         if (!MySetLayeredWindowAttributes || !(exstyle = GetWindowLong(target_window, GWL_EXSTYLE)))
  3181.             return OK;  // Do nothing on OSes that don't support it.
  3182.         if (!stricmp(aValue, "Off"))
  3183.             // One user reported that turning off the attribute helps window's scrolling performance.
  3184.             SetWindowLong(target_window, GWL_EXSTYLE, exstyle & ~WS_EX_LAYERED);
  3185.         else
  3186.         {
  3187.             if (attrib == WINSET_TRANSPARENT)
  3188.             {
  3189.                 // Update to the below for v1.0.23: WS_EX_LAYERED can now be removed via the above:
  3190.                 // NOTE: It seems best never to remove the WS_EX_LAYERED attribute, even if the value is 255
  3191.                 // (non-transparent), since the window might have had that attribute previously and may need
  3192.                 // it to function properly.  For example, an app may support making its own windows transparent
  3193.                 // but might not expect to have to turn WS_EX_LAYERED back on if we turned it off.  One drawback
  3194.                 // of this is a quote from somewhere that might or might not be accurate: "To make this window
  3195.                 // completely opaque again, remove the WS_EX_LAYERED bit by calling SetWindowLong and then ask
  3196.                 // the window to repaint. Removing the bit is desired to let the system know that it can free up
  3197.                 // some memory associated with layering and redirection."
  3198.                 value = ATOI(aValue);
  3199.                 // A little debatable, but this behavior seems best, at least in some cases:
  3200.                 if (value < 0)
  3201.                     value = 0;
  3202.                 else if (value > 255)
  3203.                     value = 255;
  3204.                 SetWindowLong(target_window, GWL_EXSTYLE, exstyle | WS_EX_LAYERED);
  3205.                 MySetLayeredWindowAttributes(target_window, 0, value, LWA_ALPHA);
  3206.             }
  3207.             else // attrib == WINSET_TRANSCOLOR
  3208.             {
  3209.                 // The reason WINSET_TRANSCOLOR accepts both the color and an optional transparency settings
  3210.                 // is that calling SetLayeredWindowAttributes() with only the LWA_COLORKEY flag causes the
  3211.                 // window to lose its current transparency setting in favor of the transparent color.  This
  3212.                 // is true even though the LWA_ALPHA flag was not specified, which seems odd and is a little
  3213.                 // disappointing, but that's the way it is on XP at least.
  3214.                 char aValue_copy[256];
  3215.                 strlcpy(aValue_copy, aValue, sizeof(aValue_copy)); // Make a modifiable copy.
  3216.                 char *space_pos = StrChrAny(aValue_copy, " \t"); // Space or tab.
  3217.                 if (space_pos)
  3218.                 {
  3219.                     *space_pos = '\0';
  3220.                     ++space_pos;  // Point it to the second substring.
  3221.                 }
  3222.                 COLORREF color = ColorNameToBGR(aValue_copy);
  3223.                 if (color == CLR_NONE) // A matching color name was not found, so assume it's in hex format.
  3224.                     // It seems strtol() automatically handles the optional leading "0x" if present:
  3225.                     color = rgb_to_bgr(strtol(aValue_copy, NULL, 16));
  3226.                 DWORD flags;
  3227.                 if (   space_pos && *(space_pos = omit_leading_whitespace(space_pos))   ) // Relies on short-circuit boolean.
  3228.                 {
  3229.                     value = ATOI(space_pos);  // To keep it simple, don't bother with 0 to 255 range validation in this case.
  3230.                     flags = LWA_COLORKEY|LWA_ALPHA;  // i.e. set both the trans-color and the transparency level.
  3231.                 }
  3232.                 else // No translucency value is present, only a trans-color.
  3233.                 {
  3234.                     value = 0;  // Init to avoid possible compiler warning.
  3235.                     flags = LWA_COLORKEY;
  3236.                 }
  3237.                 SetWindowLong(target_window, GWL_EXSTYLE, exstyle | WS_EX_LAYERED);
  3238.                 MySetLayeredWindowAttributes(target_window, color, value, flags);
  3239.             }
  3240.         }
  3241.         break;
  3242.     }
  3243.  
  3244.     case WINSET_STYLE:
  3245.     case WINSET_EXSTYLE:
  3246.     {
  3247.         if (!*aValue)
  3248.             return OK; // Seems best not to treat an explicit blank as zero. Let ErrorLevel tell the story.
  3249.         int style_index = (attrib == WINSET_STYLE) ? GWL_STYLE : GWL_EXSTYLE;
  3250.         DWORD new_style, orig_style = GetWindowLong(target_window, style_index);
  3251.         if (!strchr("+-^", *aValue))  // | and & are used instead of +/- to allow +/- to have their native function.
  3252.             new_style = ATOU(aValue); // No prefix, so this new style will entirely replace the current style.
  3253.         else
  3254.         {
  3255.             ++aValue; // Won't work combined with next line, due to next line being a macro that uses the arg twice.
  3256.             DWORD style_change = ATOU(aValue);
  3257.             // +/-/^ are used instead of |&^ because the latter is confusing, namely that
  3258.             // "&" really means &=~style, etc.
  3259.             switch(aValue[-1])
  3260.             {
  3261.             case '+': new_style = orig_style | style_change; break;
  3262.             case '-': new_style = orig_style & ~style_change; break;
  3263.             case '^': new_style = orig_style ^ style_change; break;
  3264.             }
  3265.         }
  3266.         SetLastError(0); // Prior to SetWindowLong(), as recommended by MSDN.
  3267.         if (SetWindowLong(target_window, style_index, new_style) || !GetLastError()) // This is the precise way to detect success according to MSDN.
  3268.         {
  3269.             // Even if it indicated success, sometimes it failed anyway.  Find out for sure:
  3270.             if (GetWindowLong(target_window, style_index) != orig_style) // Even a partial change counts as a success.
  3271.             {
  3272.                 // SetWindowPos is also necessary, otherwise the frame thickness entirely around the window
  3273.                 // does not get updated (just parts of it):
  3274.                 SetWindowPos(target_window, NULL, 0, 0, 0, 0, SWP_DRAWFRAME|SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
  3275.                 // Since SetWindowPos() probably doesn't know that the style changed, below is probably necessary
  3276.                 // too, at least in some cases:
  3277.                 InvalidateRect(target_window, NULL, TRUE); // Quite a few styles require this to become visibly manifest.
  3278.                 return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  3279.             }
  3280.         }
  3281.         return OK; // Since above didn't return, it's a failure.  Let ErrorLevel tell the story.
  3282.     }
  3283.  
  3284.     case WINSET_ENABLE:
  3285.     case WINSET_DISABLE: // These are separate sub-commands from WINSET_STYLE because merely changing the WS_DISABLED style is usually not as effective as calling EnableWindow().
  3286.         EnableWindow(target_window, attrib == WINSET_ENABLE);
  3287.         return OK;
  3288.  
  3289.     case WINSET_REGION:
  3290.         return WinSetRegion(target_window, aValue);
  3291.  
  3292.     case WINSET_REDRAW:
  3293.         // Seems best to always have the last param be TRUE, for now, so that aValue can be
  3294.         // reserved for future use such as invalidating only part of a window, etc. Also, it
  3295.         // seems best not to call UpdateWindow(), which forces the window to immediately
  3296.         // process a WM_PAINT message, since that might not be desirable as a default (maybe
  3297.         // an option someday).  Other future options might include alternate methods of
  3298.         // getting a window to redraw, such as:
  3299.         // SendMessage(mHwnd, WM_NCPAINT, 1, 0);
  3300.         // RedrawWindow(mHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW);
  3301.         // SetWindowPos(mHwnd, NULL, 0, 0, 0, 0, SWP_DRAWFRAME|SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
  3302.         // GetClientRect(mControl[mDefaultButtonIndex].hwnd, &client_rect);
  3303.         // InvalidateRect(mControl[mDefaultButtonIndex].hwnd, &client_rect, TRUE);
  3304.         InvalidateRect(target_window, NULL, TRUE);
  3305.         break;
  3306.  
  3307.     } // switch()
  3308.     return OK;
  3309. }
  3310.  
  3311.  
  3312.  
  3313. ResultType Line::WinSetTitle(char *aTitle, char *aText, char *aNewTitle, char *aExcludeTitle, char *aExcludeText)
  3314. // Like AutoIt2, this function and others like it always return OK, even if the target window doesn't
  3315. // exist or there action doesn't actually succeed.
  3316. {
  3317.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  3318.     if (!target_window)
  3319.         return OK;
  3320.     SetWindowText(target_window, aNewTitle);
  3321.     return OK;
  3322. }
  3323.  
  3324.  
  3325.  
  3326. ResultType Line::WinGetTitle(char *aTitle, char *aText, char *aExcludeTitle, char *aExcludeText)
  3327. {
  3328.     Var &output_var = *OUTPUT_VAR;
  3329.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  3330.     // Even if target_window is NULL, we want to continue on so that the output
  3331.     // param is set to be the empty string, which is the proper thing to do
  3332.     // rather than leaving whatever was in there before.
  3333.  
  3334.     // Handle the output parameter.  See the comments in ACT_CONTROLGETTEXT for details.
  3335.     VarSizeType space_needed = target_window ? GetWindowTextLength(target_window) + 1 : 1; // 1 for terminator.
  3336.     if (output_var.Assign(NULL, space_needed - 1) != OK)
  3337.         return FAIL;  // It already displayed the error.
  3338.     if (target_window)
  3339.     {
  3340.         // Update length using the actual length, rather than the estimate provided by GetWindowTextLength():
  3341.         output_var.Length() = (VarSizeType)GetWindowText(target_window, output_var.Contents(), space_needed);
  3342.         if (!output_var.Length())
  3343.             // There was no text to get or GetWindowTextTimeout() failed.
  3344.             *output_var.Contents() = '\0';  // Safe because Assign() gave us a non-constant memory area.
  3345.     }
  3346.     else
  3347.     {
  3348.         *output_var.Contents() = '\0';
  3349.         output_var.Length() = 0;
  3350.     }
  3351.     return output_var.Close();  // In case it's the clipboard.
  3352. }
  3353.  
  3354.  
  3355.  
  3356. ResultType Line::WinGetClass(char *aTitle, char *aText, char *aExcludeTitle, char *aExcludeText)
  3357. {
  3358.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  3359.     if (!target_window)
  3360.         return OUTPUT_VAR->Assign();
  3361.     char class_name[WINDOW_CLASS_SIZE];
  3362.     if (!GetClassName(target_window, class_name, sizeof(class_name)))
  3363.         return OUTPUT_VAR->Assign();
  3364.     return OUTPUT_VAR->Assign(class_name);
  3365. }
  3366.  
  3367.  
  3368.  
  3369. ResultType WinGetList(Var &aOutputVar, WinGetCmds aCmd, char *aTitle, char *aText, char *aExcludeTitle, char *aExcludeText)
  3370. // Helper function for WinGet() to avoid having a WindowSearch object on its stack (since that object
  3371. // normally isn't needed).
  3372. {
  3373.     WindowSearch ws;
  3374.     ws.mFindLastMatch = true; // Must set mFindLastMatch to get all matches rather than just the first.
  3375.     ws.mArrayStart = (aCmd == WINGET_CMD_LIST) ? &aOutputVar : NULL; // Provide the position in the var list of where the array-element vars will be.
  3376.     // If aTitle is ahk_id nnnn, the Enum() below will be inefficient.  However, ahk_id is almost unheard of
  3377.     // in this context because it makes little sense, so no extra code is added to make that case efficient.
  3378.     if (ws.SetCriteria(g, aTitle, aText, aExcludeTitle, aExcludeText)) // These criteria allow the possibilty of a match.
  3379.         EnumWindows(EnumParentFind, (LPARAM)&ws);
  3380.     //else leave ws.mFoundCount set to zero (by the constructor).
  3381.     return aOutputVar.Assign(ws.mFoundCount);
  3382. }
  3383.  
  3384.  
  3385.  
  3386. ResultType Line::WinGet(char *aCmd, char *aTitle, char *aText, char *aExcludeTitle, char *aExcludeText)
  3387. {
  3388.     Var &output_var = *OUTPUT_VAR;  // This is done even for WINGET_CMD_LIST.
  3389.     WinGetCmds cmd = ConvertWinGetCmd(aCmd);
  3390.     // Since command names are validated at load-time, this only happens if the command name
  3391.     // was contained in a variable reference.  But for simplicity of design here, return
  3392.     // failure in this case (unlike other functions similar to this one):
  3393.     if (cmd == WINGET_CMD_INVALID)
  3394.         return LineError(ERR_PARAM2_INVALID ERR_ABORT, FAIL, aCmd);
  3395.  
  3396.     bool target_window_determined = true;  // Set default.
  3397.     HWND target_window;
  3398.     IF_USE_FOREGROUND_WINDOW(g.DetectHiddenWindows, aTitle, aText, aExcludeTitle, aExcludeText)
  3399.     else if (!(*aTitle || *aText || *aExcludeTitle || *aExcludeText)
  3400.         && !(cmd == WINGET_CMD_LIST || cmd == WINGET_CMD_COUNT)) // v1.0.30.02/v1.0.30.03: Have "list"/"count" get all windows on the system when there are no parameters.
  3401.         target_window = GetValidLastUsedWindow(g);
  3402.     else
  3403.         target_window_determined = false;  // A different method is required.
  3404.  
  3405.     // Used with WINGET_CMD_LIST to create an array (if needed).  Make it longer than Max var name
  3406.     // so that FindOrAddVar() will be able to spot and report var names that are too long:
  3407.     char var_name[MAX_VAR_NAME_LENGTH + 20], buf[32];
  3408.     Var *array_item;
  3409.  
  3410.     switch(cmd)
  3411.     {
  3412.     case WINGET_CMD_ID:
  3413.     case WINGET_CMD_IDLAST:
  3414.         if (!target_window_determined)
  3415.             target_window = WinExist(g, aTitle, aText, aExcludeTitle, aExcludeText, cmd == WINGET_CMD_IDLAST);
  3416.         if (target_window)
  3417.             return output_var.AssignHWND(target_window);
  3418.         else
  3419.             return output_var.Assign();
  3420.  
  3421.     case WINGET_CMD_PID:
  3422.     case WINGET_CMD_PROCESSNAME:
  3423.         if (!target_window_determined)
  3424.             target_window = WinExist(g, aTitle, aText, aExcludeTitle, aExcludeText);
  3425.         if (target_window)
  3426.         {
  3427.             DWORD pid;
  3428.             GetWindowThreadProcessId(target_window, &pid);
  3429.             if (cmd == WINGET_CMD_PID)
  3430.                 return output_var.Assign(pid);
  3431.             // Otherwise, get the full path and name of the executable that owns this window.
  3432.             _ultoa(pid, buf, 10);
  3433.             char process_name[MAX_PATH];
  3434.             if (ProcessExist(buf, process_name))
  3435.                 return output_var.Assign(process_name);
  3436.         }
  3437.         // If above didn't return:
  3438.         return output_var.Assign();
  3439.  
  3440.     case WINGET_CMD_COUNT:
  3441.     case WINGET_CMD_LIST:
  3442.         // LIST retrieves a list of HWNDs for the windows that match the given criteria and stores them in
  3443.         // an array.  The number of items in the array is stored in the base array name (unlike
  3444.         // StringSplit, which stores them in array element #0).  This is done for performance reasons
  3445.         // (so that element #0 doesn't have to be looked up at runtime), but mostly because of the
  3446.         // complexity of resolving a parameter than can be either an output-var or an array name at
  3447.         // load-time -- namely that if param #1 were allowed to be an array name, there is ambiguity
  3448.         // about where the name of the array is actually stored depending on whether param#1 was literal
  3449.         // text or a deref.  So it's easier and performs better just to do it this way, even though it
  3450.         // breaks from the StringSplit tradition:
  3451.         if (target_window_determined)
  3452.         {
  3453.             if (!target_window)
  3454.                 return output_var.Assign("0"); // 0 windows found
  3455.             if (cmd == WINGET_CMD_LIST)
  3456.             {
  3457.                 // Otherwise, since the target window has been determined, we know that it is
  3458.                 // the only window to be put into the array:
  3459.                 if (   !(array_item = g_script.FindOrAddVar(var_name
  3460.                     , snprintf(var_name, sizeof(var_name), "%s1", output_var.mName)
  3461.                     , output_var.IsLocal() ? ALWAYS_USE_LOCAL : ALWAYS_USE_GLOBAL))   )  // Find or create element #1.
  3462.                     return FAIL;  // It will have already displayed the error.
  3463.                 if (!array_item->AssignHWND(target_window))
  3464.                     return FAIL;
  3465.             }
  3466.             return output_var.Assign("1");  // 1 window found
  3467.         }
  3468.         // Otherwise, the target window(s) have not yet been determined and a special method
  3469.         // is required to gather them.
  3470.         return WinGetList(output_var, cmd, aTitle, aText, aExcludeTitle, aExcludeText); // Outsourced to avoid having a WindowSearch object on this function's stack.
  3471.  
  3472.     case WINGET_CMD_MINMAX:
  3473.         if (!target_window_determined)
  3474.             target_window = WinExist(g, aTitle, aText, aExcludeTitle, aExcludeText);
  3475.         // Testing shows that it's not possible for a minimized window to also be maximized (under
  3476.         // the theory that upon restoration, it *would* be maximized.  This is unfortunate if there
  3477.         // is no other way to determine what the restoration size and maxmized state will be for a
  3478.         // minimized window.
  3479.         if (target_window)
  3480.             return output_var.Assign(IsZoomed(target_window) ? 1 : (IsIconic(target_window) ? -1 : 0));
  3481.         else
  3482.             return output_var.Assign();
  3483.  
  3484.     case WINGET_CMD_CONTROLLIST:
  3485.     case WINGET_CMD_CONTROLLISTHWND:
  3486.         if (!target_window_determined)
  3487.             target_window = WinExist(g, aTitle, aText, aExcludeTitle, aExcludeText);
  3488.         return target_window ? WinGetControlList(output_var, target_window, cmd == WINGET_CMD_CONTROLLISTHWND)
  3489.             : output_var.Assign();
  3490.  
  3491.     case WINGET_CMD_STYLE:
  3492.     case WINGET_CMD_EXSTYLE:
  3493.         if (!target_window_determined)
  3494.             target_window = WinExist(g, aTitle, aText, aExcludeTitle, aExcludeText);
  3495.         if (!target_window)
  3496.             return output_var.Assign();
  3497.         sprintf(buf, "0x%08X", GetWindowLong(target_window, cmd == WINGET_CMD_STYLE ? GWL_STYLE : GWL_EXSTYLE));
  3498.         return output_var.Assign(buf);
  3499.  
  3500.     case WINGET_CMD_TRANSPARENT:
  3501.     case WINGET_CMD_TRANSCOLOR:
  3502.         if (!target_window_determined)
  3503.             target_window = WinExist(g, aTitle, aText, aExcludeTitle, aExcludeText);
  3504.         if (!target_window)
  3505.             return output_var.Assign();
  3506.         typedef BOOL (WINAPI *MyGetLayeredWindowAttributesType)(HWND, COLORREF*, BYTE*, DWORD*);
  3507.         static MyGetLayeredWindowAttributesType MyGetLayeredWindowAttributes = (MyGetLayeredWindowAttributesType)
  3508.             GetProcAddress(GetModuleHandle("user32"), "GetLayeredWindowAttributes");
  3509.         COLORREF color;
  3510.         BYTE alpha;
  3511.         DWORD flags;
  3512.         // IMPORTANT (when considering future enhancements to these commands): Unlike
  3513.         // SetLayeredWindowAttributes(), which works on Windows 2000, GetLayeredWindowAttributes()
  3514.         // is supported only on XP or later.
  3515.         if (!MyGetLayeredWindowAttributes || !(MyGetLayeredWindowAttributes(target_window, &color, &alpha, &flags)))
  3516.             return output_var.Assign();
  3517.         if (cmd == WINGET_CMD_TRANSPARENT)
  3518.             return (flags & LWA_ALPHA) ? output_var.Assign((DWORD)alpha) : output_var.Assign();
  3519.         else // WINGET_CMD_TRANSCOLOR
  3520.         {
  3521.             if (flags & LWA_COLORKEY)
  3522.             {
  3523.                 // Store in hex format to aid in debugging scripts.  Also, the color is always
  3524.                 // stored in RGB format, since that's what WinSet uses:
  3525.                 sprintf(buf, "0x%06X", bgr_to_rgb(color));
  3526.                 return output_var.Assign(buf);
  3527.             }
  3528.             else // This window does not have a transparent color (or it's not accessible to us, perhaps for reasons described at MSDN GetLayeredWindowAttributes()).
  3529.                 return output_var.Assign();
  3530.         }
  3531.     }
  3532.  
  3533.     return FAIL;  // Never executed (increases maintainability and avoids compiler warning).
  3534. }
  3535.  
  3536.  
  3537.  
  3538. ResultType Line::WinGetControlList(Var &aOutputVar, HWND aTargetWindow, bool aFetchHWNDs)
  3539. // Caller must ensure that aTargetWindow is non-NULL and valid.
  3540. // Every control is fetched rather than just a list of distinct class names (possibly with a
  3541. // second script array containing the quantity of each class) because it's conceivable that the
  3542. // z-order of the controls will be useful information to some script authors.
  3543. // A delimited list is used rather than the array technique used by "WinGet, OutputVar, List" because:
  3544. // 1) It allows the flexibility of searching the list more easily with something like IfInString.
  3545. // 2) It seems rather rare that the count of items in the list would be useful info to a script author
  3546. //    (the count can be derived with a parsing loop if it's ever needed).
  3547. // 3) It saves memory since script arrays are permanent and since each array element would incur
  3548. //    the overhead of being a script variable, not to mention that each variable has a minimum
  3549. //    capacity (additional overhead) of 64 bytes.
  3550. {
  3551.     control_list_type cl; // A big struct containing room to store class names and counts for each.
  3552.     CL_INIT_CONTROL_LIST(cl)
  3553.     cl.fetch_hwnds = aFetchHWNDs;
  3554.     cl.target_buf = NULL;  // First pass: Signal it not not to write to the buf, but instead only calculate the length.
  3555.     EnumChildWindows(aTargetWindow, EnumChildGetControlList, (LPARAM)&cl);
  3556.     if (!cl.total_length) // No controls in the window.
  3557.         return aOutputVar.Assign();
  3558.     // This adjustment was added because someone reported that max variable capacity was being
  3559.     // exceeded in some cases (perhaps custom controls that retrieve large amounts of text
  3560.     // from the disk in response to the "get text" message):
  3561.     if (cl.total_length >= g_MaxVarCapacity) // Allow the command to succeed by truncating the text.
  3562.         cl.total_length = g_MaxVarCapacity - 1;
  3563.     // Set up the var, enlarging it if necessary.  If the aOutputVar is of type VAR_CLIPBOARD,
  3564.     // this call will set up the clipboard for writing:
  3565.     if (aOutputVar.Assign(NULL, (VarSizeType)cl.total_length) != OK)
  3566.         return FAIL;  // It already displayed the error.
  3567.     // Fetch the text directly into the var.  Also set the length explicitly
  3568.     // in case actual size written was off from the esimated size (in case the list of
  3569.     // controls changed in the split second between pass #1 and pass #2):
  3570.     CL_INIT_CONTROL_LIST(cl)
  3571.     cl.target_buf = aOutputVar.Contents();  // Second pass: Write to the buffer.
  3572.     cl.capacity = aOutputVar.Capacity(); // Because granted capacity might be a little larger than we asked for.
  3573.     EnumChildWindows(aTargetWindow, EnumChildGetControlList, (LPARAM)&cl);
  3574.     aOutputVar.Length() = (VarSizeType)cl.total_length;  // In case it wound up being smaller than expected.
  3575.     if (!cl.total_length) // Something went wrong, so make sure its terminated just in case.
  3576.         *aOutputVar.Contents() = '\0';  // Safe because Assign() gave us a non-constant memory area.
  3577.     return aOutputVar.Close();  // In case it's the clipboard.
  3578. }
  3579.  
  3580.  
  3581.  
  3582. BOOL CALLBACK EnumChildGetControlList(HWND aWnd, LPARAM lParam)
  3583. {
  3584.     control_list_type &cl = *(control_list_type *)lParam;  // For performance and convenience.
  3585.     char line[WINDOW_CLASS_SIZE + 5];  // +5 to allow room for the sequence number to be appended later below.
  3586.     int line_length;
  3587.  
  3588.     // cl.fetch_hwnds==true is a new mode in v1.0.43.06+ to help performance of AHK Window Info and other
  3589.     // scripts that want to operate directly on the HWNDs.
  3590.     if (cl.fetch_hwnds)
  3591.     {
  3592.         line[0] = '0';
  3593.         line[1] = 'x';
  3594.         line_length = 2 + (int)strlen(_ui64toa((unsigned __int64)aWnd, line + 2, 16));
  3595.     }
  3596.     else // The mode that fetches ClassNN vs. HWND.
  3597.     {
  3598.         // Note: IsWindowVisible(aWnd) is not checked because although Window Spy does not reveal
  3599.         // hidden controls if the mouse happens to be hovering over one, it does include them in its
  3600.         // sequence numbering (which is a relieve, since results are probably much more consistent
  3601.         // then, esp. for apps that hide and unhide controls in response to actions on other controls).
  3602.         if (  !(line_length = GetClassName(aWnd, line, WINDOW_CLASS_SIZE))   ) // Don't include the +5 extra size since that is reserved for seq. number.
  3603.             return TRUE; // Probably very rare. Continue enumeration since Window Spy doesn't even check for failure.
  3604.         // It has been verified that GetClassName()'s returned length does not count the terminator.
  3605.  
  3606.         // Check if this class already exists in the class array:
  3607.         int class_index;
  3608.         for (class_index = 0; class_index < cl.total_classes; ++class_index)
  3609.             if (!stricmp(cl.class_name[class_index], line)) // lstrcmpi() is not used: 1) avoids breaking exisitng scripts; 2) provides consistent behavior across multiple locales.
  3610.                 break;
  3611.         if (class_index < cl.total_classes) // Match found.
  3612.         {
  3613.             ++cl.class_count[class_index]; // Increment the number of controls of this class that have been found so far.
  3614.             if (cl.class_count[class_index] > 99999) // Sanity check; prevents buffer overflow or number truncation in "line".
  3615.                 return TRUE;  // Continue the enumeration.
  3616.         }
  3617.         else // No match found, so create new entry if there's room.
  3618.         {
  3619.             if (cl.total_classes == CL_MAX_CLASSES // No pointers left.
  3620.                 || CL_CLASS_BUF_SIZE - (cl.buf_free_spot - cl.class_buf) - 1 < line_length) // Insuff. room in buf.
  3621.                 return TRUE; // Very rare. Continue the enumeration so that class names already found can be collected.
  3622.             // Otherwise:
  3623.             cl.class_name[class_index] = cl.buf_free_spot;  // Set this pointer to its place in the buffer.
  3624.             strcpy(cl.class_name[class_index], line); // Copy the string into this place.
  3625.             cl.buf_free_spot += line_length + 1;  // +1 because every string in the buf needs its own terminator.
  3626.             cl.class_count[class_index] = 1;  // Indicate that the quantity of this class so far is 1.
  3627.             ++cl.total_classes;
  3628.         }
  3629.  
  3630.         _itoa(cl.class_count[class_index], line + line_length, 10); // Append the seq. number to line.
  3631.         line_length = (int)strlen(line);  // Update the length.
  3632.     }
  3633.  
  3634.     int extra_length;
  3635.     if (cl.is_first_iteration)
  3636.     {
  3637.         extra_length = 0; // All items except the first are preceded by a delimiting LF.
  3638.         cl.is_first_iteration = false;
  3639.     }
  3640.     else
  3641.         extra_length = 1;
  3642.  
  3643.     if (cl.target_buf)
  3644.     {
  3645.         if ((int)(cl.capacity - cl.total_length - extra_length - 1) < line_length)
  3646.             // No room in target_buf (i.e. don't write a partial item to the buffer).
  3647.             return TRUE;  // Rare: it should only happen if size in pass #2 differed from that calc'd in pass #1.
  3648.         if (extra_length)
  3649.         {
  3650.             cl.target_buf[cl.total_length] = '\n'; // Replace previous item's terminator with newline.
  3651.             cl.total_length += extra_length;
  3652.         }
  3653.         strcpy(cl.target_buf + cl.total_length, line); // Write hwnd or class name+seq. number.
  3654.         cl.total_length += line_length;
  3655.     }
  3656.     else // Caller only wanted the total length calculated.
  3657.         cl.total_length += line_length + extra_length;
  3658.  
  3659.     return TRUE; // Continue enumeration through all the windows.
  3660. }
  3661.  
  3662.  
  3663.  
  3664. ResultType Line::WinGetText(char *aTitle, char *aText, char *aExcludeTitle, char *aExcludeText)
  3665. {
  3666.     Var &output_var = *OUTPUT_VAR;
  3667.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  3668.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  3669.     // Even if target_window is NULL, we want to continue on so that the output
  3670.     // variables are set to be the empty string, which is the proper thing to do
  3671.     // rather than leaving whatever was in there before:
  3672.     if (!target_window)
  3673.         return output_var.Assign(); // Tell it not to free the memory by not calling with "".
  3674.  
  3675.     length_and_buf_type sab;
  3676.     sab.buf = NULL; // Tell it just to calculate the length this time around.
  3677.     sab.total_length = 0; // Init
  3678.     sab.capacity = 0;     //
  3679.     EnumChildWindows(target_window, EnumChildGetText, (LPARAM)&sab);
  3680.  
  3681.     if (!sab.total_length) // No text in window.
  3682.     {
  3683.         g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  3684.         return output_var.Assign(); // Tell it not to free the memory by omitting all params.
  3685.     }
  3686.     // This adjustment was added because someone reported that max variable capacity was being
  3687.     // exceeded in some cases (perhaps custom controls that retrieve large amounts of text
  3688.     // from the disk in response to the "get text" message):
  3689.     if (sab.total_length >= g_MaxVarCapacity)    // Allow the command to succeed by truncating the text.
  3690.         sab.total_length = g_MaxVarCapacity - 1; // And this length will be used to limit the retrieval capacity below.
  3691.  
  3692.     // Set up the var, enlarging it if necessary.  If the output_var is of type VAR_CLIPBOARD,
  3693.     // this call will set up the clipboard for writing:
  3694.     if (output_var.Assign(NULL, (VarSizeType)sab.total_length) != OK)
  3695.         return FAIL;  // It already displayed the error.
  3696.  
  3697.     // Fetch the text directly into the var.  Also set the length explicitly
  3698.     // in case actual size written was different than the esimated size (since
  3699.     // GetWindowTextLength() can return more space that will actually be required
  3700.     // in certain circumstances, see MSDN):
  3701.     sab.buf = output_var.Contents();
  3702.     sab.total_length = 0; // Init
  3703.     // Note: The capacity member below exists because granted capacity might be a little larger than we asked for,
  3704.     // which allows the actual text fetched to be larger than the length estimate retrieved by the first pass
  3705.     // (which generally shouldn't happen since MSDN docs say that the actual length can be less, but never greater,
  3706.     // than the estimate length):
  3707.     sab.capacity = output_var.Capacity(); // Capacity includes the zero terminator, i.e. it's the size of the memory area.
  3708.     EnumChildWindows(target_window, EnumChildGetText, (LPARAM)&sab); // Get the text.
  3709.  
  3710.     // Length is set explicitly below in case it wound up being smaller than expected/estimated.
  3711.     // MSDN says that can happen generally, and also specifically because: "ANSI applications may have
  3712.     // the string in the buffer reduced in size (to a minimum of half that of the wParam value) due to
  3713.     // conversion from ANSI to Unicode."
  3714.     output_var.Length() = (VarSizeType)sab.total_length;
  3715.     if (sab.total_length)
  3716.         g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  3717.     else // Something went wrong, so make sure we set to empty string.
  3718.         *sab.buf = '\0';  // Safe because Assign() gave us a non-constant memory area.
  3719.     return output_var.Close();  // In case it's the clipboard.
  3720. }
  3721.  
  3722.  
  3723.  
  3724. BOOL CALLBACK EnumChildGetText(HWND aWnd, LPARAM lParam)
  3725. {
  3726.     if (!g.DetectHiddenText && !IsWindowVisible(aWnd))
  3727.         return TRUE;  // This child/control is hidden and user doesn't want it considered, so skip it.
  3728.     length_and_buf_type &lab = *(length_and_buf_type *)lParam;  // For performance and convenience.
  3729.     int length;
  3730.     if (lab.buf)
  3731.         length = GetWindowTextTimeout(aWnd, lab.buf + lab.total_length
  3732.             , (int)(lab.capacity - lab.total_length)); // Not +1.  Verified correct because WM_GETTEXT accepts size of buffer, not its length.
  3733.     else
  3734.         length = GetWindowTextTimeout(aWnd);
  3735.     lab.total_length += length;
  3736.     if (length)
  3737.     {
  3738.         if (lab.buf)
  3739.         {
  3740.             if (lab.capacity - lab.total_length > 2) // Must be >2 due to zero terminator.
  3741.             {
  3742.                 strcpy(lab.buf + lab.total_length, "\r\n"); // Something to delimit each control's text.
  3743.                 lab.total_length += 2;
  3744.             }
  3745.             // else don't increment total_length
  3746.         }
  3747.         else
  3748.             lab.total_length += 2; // Since buf is NULL, accumulate the size that *would* be needed.
  3749.     }
  3750.     return TRUE; // Continue enumeration through all the child windows of this parent.
  3751. }
  3752.  
  3753.  
  3754.  
  3755. ResultType Line::WinGetPos(char *aTitle, char *aText, char *aExcludeTitle, char *aExcludeText)
  3756. {
  3757.     Var *output_var_x = ARGVAR1;  // Ok if NULL.
  3758.     Var *output_var_y = ARGVAR2;  // Ok if NULL.
  3759.     Var *output_var_width = ARGVAR3;  // Ok if NULL.
  3760.     Var *output_var_height = ARGVAR4;  // Ok if NULL.
  3761.  
  3762.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  3763.     // Even if target_window is NULL, we want to continue on so that the output
  3764.     // variables are set to be the empty string, which is the proper thing to do
  3765.     // rather than leaving whatever was in there before.
  3766.     RECT rect;
  3767.     if (target_window)
  3768.         GetWindowRect(target_window, &rect);
  3769.     else // ensure it's initialized for possible later use:
  3770.         rect.bottom = rect.left = rect.right = rect.top = 0;
  3771.  
  3772.     ResultType result = OK; // Set default;
  3773.  
  3774.     if (output_var_x)
  3775.         if (target_window)
  3776.         {
  3777.             if (!output_var_x->Assign(rect.left))  // X position
  3778.                 result = FAIL;
  3779.         }
  3780.         else
  3781.             if (!output_var_x->Assign(""))
  3782.                 result = FAIL;
  3783.     if (output_var_y)
  3784.         if (target_window)
  3785.         {
  3786.             if (!output_var_y->Assign(rect.top))  // Y position
  3787.                 result = FAIL;
  3788.         }
  3789.         else
  3790.             if (!output_var_y->Assign(""))
  3791.                 result = FAIL;
  3792.     if (output_var_width) // else user didn't want this value saved to an output param
  3793.         if (target_window)
  3794.         {
  3795.             if (!output_var_width->Assign(rect.right - rect.left))  // Width
  3796.                 result = FAIL;
  3797.         }
  3798.         else
  3799.             if (!output_var_width->Assign("")) // Set it to be empty to signal the user that the window wasn't found.
  3800.                 result = FAIL;
  3801.     if (output_var_height)
  3802.         if (target_window)
  3803.         {
  3804.             if (!output_var_height->Assign(rect.bottom - rect.top))  // Height
  3805.                 result = FAIL;
  3806.         }
  3807.         else
  3808.             if (!output_var_height->Assign(""))
  3809.                 result = FAIL;
  3810.  
  3811.     return result;
  3812. }
  3813.  
  3814.  
  3815.  
  3816. ResultType Line::EnvGet(char *aEnvVarName)
  3817. {
  3818.     // Don't use a size greater than 32767 because that will cause it to fail on Win95 (tested by Robert Yalkin).
  3819.     // According to MSDN, 32767 is exactly large enough to handle the largest variable plus its zero terminator.
  3820.     char buf[32767];
  3821.     // GetEnvironmentVariable() could be called twice, the first time to get the actual size.  But that would
  3822.     // probably perform worse since GetEnvironmentVariable() is a very slow function.  In addition, it would
  3823.     // add code complexity, so it seems best to fetch it into a large buffer then just copy it to dest-var.
  3824.     DWORD length = GetEnvironmentVariable(aEnvVarName, buf, sizeof(buf));
  3825.     return OUTPUT_VAR->Assign(length ? buf : "", length);
  3826. }
  3827.  
  3828.  
  3829.  
  3830. ResultType Line::SysGet(char *aCmd, char *aValue)
  3831. // Thanks to Gregory F. Hogg of Hogg's Software for providing sample code on which this function
  3832. // is based.
  3833. {
  3834.     // For simplicity and array look-up performance, this is done even for sub-commands that output to an array:
  3835.     Var &output_var = *OUTPUT_VAR;
  3836.     SysGetCmds cmd = ConvertSysGetCmd(aCmd);
  3837.     // Since command names are validated at load-time, this only happens if the command name
  3838.     // was contained in a variable reference.  But for simplicity of design here, return
  3839.     // failure in this case (unlike other functions similar to this one):
  3840.     if (cmd == SYSGET_CMD_INVALID)
  3841.         return LineError(ERR_PARAM2_INVALID ERR_ABORT, FAIL, aCmd);
  3842.  
  3843.     MonitorInfoPackage mip = {0};  // Improves maintainability to initialize unconditionally, here.
  3844.     mip.monitor_info_ex.cbSize = sizeof(MONITORINFOEX); // Also improves maintainability.
  3845.  
  3846.     // EnumDisplayMonitors() must be dynamically loaded; otherwise, the app won't launch at all on Win95/NT.
  3847.     typedef BOOL (WINAPI* EnumDisplayMonitorsType)(HDC, LPCRECT, MONITORENUMPROC, LPARAM);
  3848.     static EnumDisplayMonitorsType MyEnumDisplayMonitors = (EnumDisplayMonitorsType)
  3849.         GetProcAddress(GetModuleHandle("user32"), "EnumDisplayMonitors");
  3850.  
  3851.     switch(cmd)
  3852.     {
  3853.     case SYSGET_CMD_METRICS: // In this case, aCmd is the value itself.
  3854.         return output_var.Assign(GetSystemMetrics(ATOI(aCmd)));  // Input and output are both signed integers.
  3855.  
  3856.     // For the next few cases, I'm not sure if it is possible to have zero monitors.  Obviously it's possible
  3857.     // to not have a monitor turned on or not connected at all.  But it seems likely that these various API
  3858.     // functions will provide a "default monitor" in the absence of a physical monitor connected to the
  3859.     // system.  To be safe, all of the below will assume that zero is possible, at least on some OSes or
  3860.     // under some conditions.  However, on Win95/NT, "1" is assumed since there is probably no way to tell
  3861.     // for sure if there are zero monitors except via GetSystemMetrics(SM_CMONITORS), which is a different
  3862.     // animal as described below.
  3863.     case SYSGET_CMD_MONITORCOUNT:
  3864.         // Don't use GetSystemMetrics(SM_CMONITORS) because of this:
  3865.         // MSDN: "GetSystemMetrics(SM_CMONITORS) counts only display monitors. This is different from
  3866.         // EnumDisplayMonitors, which enumerates display monitors and also non-display pseudo-monitors."
  3867.         if (!MyEnumDisplayMonitors) // Since system only supports 1 monitor, the first must be primary.
  3868.             return output_var.Assign(1); // Assign as 1 vs. "1" to use hexidecimal display if that is in effect.
  3869.         mip.monitor_number_to_find = COUNT_ALL_MONITORS;
  3870.         MyEnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&mip);
  3871.         return output_var.Assign(mip.count); // Will assign zero if the API ever returns a legitimate zero.
  3872.  
  3873.     // Even if the first monitor to be retrieved by the EnumProc is always the primary (which is doubtful
  3874.     // since there's no mention of this in the MSDN docs) it seems best to have this sub-cmd in case that
  3875.     // policy ever changes:
  3876.     case SYSGET_CMD_MONITORPRIMARY:
  3877.         if (!MyEnumDisplayMonitors) // Since system only supports 1 monitor, the first must be primary.
  3878.             return output_var.Assign(1); // Assign as 1 vs. "1" to use hexidecimal display if that is in effect.
  3879.         // The mip struct's values have already initalized correctly for the below:
  3880.         MyEnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&mip);
  3881.         return output_var.Assign(mip.count); // Will assign zero if the API ever returns a legitimate zero.
  3882.  
  3883.     case SYSGET_CMD_MONITORAREA:
  3884.     case SYSGET_CMD_MONITORWORKAREA:
  3885.         Var *output_var_left, *output_var_top, *output_var_right, *output_var_bottom;
  3886.         // Make it longer than max var name so that FindOrAddVar() will be able to spot and report
  3887.         // var names that are too long:
  3888.         char var_name[MAX_VAR_NAME_LENGTH + 20];
  3889.         // To help performance (in case the linked list of variables is huge), tell FindOrAddVar where
  3890.         // to start the search.  Use the base array name rather than the preceding element because,
  3891.         // for example, Array19 is alphabetially less than Array2, so we can't rely on the
  3892.         // numerical ordering:
  3893.         int always_use;
  3894.         always_use = output_var.IsLocal() ? ALWAYS_USE_LOCAL : ALWAYS_USE_GLOBAL;
  3895.         if (   !(output_var_left = g_script.FindOrAddVar(var_name
  3896.             , snprintf(var_name, sizeof(var_name), "%sLeft", output_var.mName)
  3897.             , always_use))   )
  3898.             return FAIL;  // It already reported the error.
  3899.         if (   !(output_var_top = g_script.FindOrAddVar(var_name
  3900.             , snprintf(var_name, sizeof(var_name), "%sTop", output_var.mName)
  3901.             , always_use))   )
  3902.             return FAIL;
  3903.         if (   !(output_var_right = g_script.FindOrAddVar(var_name
  3904.             , snprintf(var_name, sizeof(var_name), "%sRight", output_var.mName)
  3905.             , always_use))   )
  3906.             return FAIL;
  3907.         if (   !(output_var_bottom = g_script.FindOrAddVar(var_name
  3908.             , snprintf(var_name, sizeof(var_name), "%sBottom", output_var.mName), always_use))   )
  3909.             return FAIL;
  3910.  
  3911.         RECT monitor_rect;
  3912.         if (MyEnumDisplayMonitors)
  3913.         {
  3914.             mip.monitor_number_to_find = ATOI(aValue);  // If this returns 0, it will default to the primary monitor.
  3915.             MyEnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&mip);
  3916.             if (!mip.count || (mip.monitor_number_to_find && mip.monitor_number_to_find != mip.count))
  3917.             {
  3918.                 // With the exception of the caller having specified a non-existent monitor number, all of
  3919.                 // the ways the above can happen are probably impossible in practice.  Make all the variables
  3920.                 // blank vs. zero to indicate the problem.
  3921.                 output_var_left->Assign();
  3922.                 output_var_top->Assign();
  3923.                 output_var_right->Assign();
  3924.                 output_var_bottom->Assign();
  3925.                 return OK;
  3926.             }
  3927.             // Otherwise:
  3928.             monitor_rect = (cmd == SYSGET_CMD_MONITORAREA) ? mip.monitor_info_ex.rcMonitor : mip.monitor_info_ex.rcWork;
  3929.         }
  3930.         else // Win95/NT: Since system only supports 1 monitor, the first must be primary.
  3931.         {
  3932.             if (cmd == SYSGET_CMD_MONITORAREA)
  3933.             {
  3934.                 monitor_rect.left = 0;
  3935.                 monitor_rect.top = 0;
  3936.                 monitor_rect.right = GetSystemMetrics(SM_CXSCREEN);
  3937.                 monitor_rect.bottom = GetSystemMetrics(SM_CYSCREEN);
  3938.             }
  3939.             else // Work area
  3940.                 SystemParametersInfo(SPI_GETWORKAREA, 0, &monitor_rect, 0);  // Get desktop rect excluding task bar.
  3941.         }
  3942.         output_var_left->Assign(monitor_rect.left);
  3943.         output_var_top->Assign(monitor_rect.top);
  3944.         output_var_right->Assign(monitor_rect.right);
  3945.         output_var_bottom->Assign(monitor_rect.bottom);
  3946.         return OK;
  3947.  
  3948.     case SYSGET_CMD_MONITORNAME:
  3949.         if (MyEnumDisplayMonitors)
  3950.         {
  3951.             mip.monitor_number_to_find = ATOI(aValue);  // If this returns 0, it will default to the primary monitor.
  3952.             MyEnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&mip);
  3953.             if (!mip.count || (mip.monitor_number_to_find && mip.monitor_number_to_find != mip.count))
  3954.                 // With the exception of the caller having specified a non-existent monitor number, all of
  3955.                 // the ways the above can happen are probably impossible in practice.  Make the variable
  3956.                 // blank to indicate the problem:
  3957.                 return output_var.Assign();
  3958.             else
  3959.                 return output_var.Assign(mip.monitor_info_ex.szDevice);
  3960.         }
  3961.         else // Win95/NT: There is probably no way to find out the name of the monitor.
  3962.             return output_var.Assign();
  3963.     } // switch()
  3964.  
  3965.     return FAIL;  // Never executed (increases maintainability and avoids compiler warning).
  3966. }
  3967.  
  3968.  
  3969.  
  3970. BOOL CALLBACK EnumMonitorProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM lParam)
  3971. {
  3972.     MonitorInfoPackage &mip = *(MonitorInfoPackage *)lParam;  // For performance and convenience.
  3973.     if (mip.monitor_number_to_find == COUNT_ALL_MONITORS)
  3974.     {
  3975.         ++mip.count;
  3976.         return TRUE;  // Enumerate all monitors so that they can be counted.
  3977.     }
  3978.     // GetMonitorInfo() must be dynamically loaded; otherwise, the app won't launch at all on Win95/NT.
  3979.     typedef BOOL (WINAPI* GetMonitorInfoType)(HMONITOR, LPMONITORINFO);
  3980.     static GetMonitorInfoType MyGetMonitorInfo = (GetMonitorInfoType)
  3981.         GetProcAddress(GetModuleHandle("user32"), "GetMonitorInfoA");
  3982.     if (!MyGetMonitorInfo) // Shouldn't normally happen since caller wouldn't have called us if OS is Win95/NT. 
  3983.         return FALSE;
  3984.     if (!MyGetMonitorInfo(hMonitor, &mip.monitor_info_ex)) // Failed.  Probably very rare.
  3985.         return FALSE; // Due to the complexity of needing to stop at the correct monitor number, do not continue.
  3986.         // In the unlikely event that the above fails when the caller wanted us to find the primary
  3987.         // monitor, the caller will think the primary is the previously found monitor (if any).
  3988.         // This is just documented here as a known limitation since this combination of circumstances
  3989.         // is probably impossible.
  3990.     ++mip.count; // So that caller can detect failure, increment only now that failure conditions have been checked.
  3991.     if (mip.monitor_number_to_find) // Caller gave a specific monitor number, so don't search for the primary monitor.
  3992.     {
  3993.         if (mip.count == mip.monitor_number_to_find) // Since the desired monitor has been found, must not continue.
  3994.             return FALSE;
  3995.     }
  3996.     else // Caller wants the primary monitor found.
  3997.         // MSDN docs are unclear that MONITORINFOF_PRIMARY is a bitwise value, but the name "dwFlags" implies so:
  3998.         if (mip.monitor_info_ex.dwFlags & MONITORINFOF_PRIMARY)
  3999.             return FALSE;  // Primary has been found and "count" contains its number. Must not continue the enumeration.
  4000.             // Above assumes that it is impossible to not have a primary monitor in a system that has at least
  4001.             // one monitor.  MSDN certainly implies this through multiple references to the primary monitor.
  4002.     // Otherwise, continue the enumeration:
  4003.     return TRUE;
  4004. }
  4005.  
  4006.  
  4007.  
  4008. LPCOLORREF getbits(HBITMAP ahImage, HDC hdc, LONG &aWidth, LONG &aHeight, bool &aIs16Bit, int aMinColorDepth = 8)
  4009. // Helper function used by PixelSearch below.
  4010. // Returns an array of pixels to the caller, which it must free when done.  Returns NULL on failure,
  4011. // in which case the contents of the output parameters is indeterminate.
  4012. {
  4013.     HDC tdc = CreateCompatibleDC(hdc);
  4014.     if (!tdc)
  4015.         return NULL;
  4016.  
  4017.     // From this point on, "goto end" will assume tdc is non-NULL, but that the below
  4018.     // might still be NULL.  Therefore, all of the following must be initialized so that the "end"
  4019.     // label can detect them:
  4020.     HGDIOBJ tdc_orig_select = NULL;
  4021.     LPCOLORREF image_pixel = NULL;
  4022.     bool success = false;
  4023.  
  4024.     // Confirmed:
  4025.     // Needs extra memory to prevent buffer overflow due to: "A bottom-up DIB is specified by setting
  4026.     // the height to a positive number, while a top-down DIB is specified by setting the height to a
  4027.     // negative number. THE BITMAP COLOR TABLE WILL BE APPENDED to the BITMAPINFO structure."
  4028.     // Maybe this applies only to negative height, in which case the second call to GetDIBits()
  4029.     // below uses one.
  4030.     struct BITMAPINFO3
  4031.     {
  4032.         BITMAPINFOHEADER    bmiHeader;
  4033.         RGBQUAD             bmiColors[260];  // v1.0.40.10: 260 vs. 3 to allow room for color table when color depth is 8-bit or less.
  4034.     } bmi;
  4035.  
  4036.     bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  4037.     bmi.bmiHeader.biBitCount = 0; // i.e. "query bitmap attributes" only.
  4038.     if (!GetDIBits(tdc, ahImage, 0, 0, NULL, (LPBITMAPINFO)&bmi, DIB_RGB_COLORS)
  4039.         || bmi.bmiHeader.biBitCount < aMinColorDepth) // Relies on short-circuit boolean order.
  4040.         goto end;
  4041.  
  4042.     // Set output parameters for caller:
  4043.     aIs16Bit = (bmi.bmiHeader.biBitCount == 16);
  4044.     aWidth = bmi.bmiHeader.biWidth;
  4045.     aHeight = bmi.bmiHeader.biHeight;
  4046.  
  4047.     int image_pixel_count = aWidth * aHeight;
  4048.     if (   !(image_pixel = (LPCOLORREF)malloc(image_pixel_count * sizeof(COLORREF)))   )
  4049.         goto end;
  4050.  
  4051.     // v1.0.40.10: To preserve compatibility with callers who check for transparency in icons, don't do any
  4052.     // of the extra color table handling for 1-bpp images.  Update: For code simplification, support only
  4053.     // 8-bpp images.  If ever support lower color depths, use something like "bmi.bmiHeader.biBitCount > 1
  4054.     // && bmi.bmiHeader.biBitCount < 9";
  4055.     bool is_8bit = (bmi.bmiHeader.biBitCount == 8);
  4056.     if (!is_8bit)
  4057.         bmi.bmiHeader.biBitCount = 32;
  4058.     bmi.bmiHeader.biHeight = -bmi.bmiHeader.biHeight; // Storing a negative inside the bmiHeader struct is a signal for GetDIBits().
  4059.  
  4060.     // Must be done only after GetDIBits() because: "The bitmap identified by the hbmp parameter
  4061.     // must not be selected into a device context when the application calls GetDIBits()."
  4062.     // (Although testing shows it works anyway, perhaps because GetDIBits() above is being
  4063.     // called in its informational mode only).
  4064.     // Note that this seems to return NULL sometimes even though everything still works.
  4065.     // Perhaps that is normal.
  4066.     tdc_orig_select = SelectObject(tdc, ahImage); // Returns NULL when we're called the second time?
  4067.  
  4068.     // Appparently there is no need to specify DIB_PAL_COLORS below when color depth is 8-bit because
  4069.     // DIB_RGB_COLORS also retrieves the color indices.
  4070.     if (   !(GetDIBits(tdc, ahImage, 0, aHeight, image_pixel, (LPBITMAPINFO)&bmi, DIB_RGB_COLORS))   )
  4071.         goto end;
  4072.  
  4073.     if (is_8bit) // This section added in v1.0.40.10.
  4074.     {
  4075.         // Convert the color indicies to RGB colors by going through the array in reverse order.
  4076.         // Reverse order allows an in-place conversion of each 8-bit color index to its corresponding
  4077.         // 32-bit RGB color.
  4078.         LPDWORD palette = (LPDWORD)_alloca(256 * sizeof(PALETTEENTRY));
  4079.         GetSystemPaletteEntries(tdc, 0, 256, (LPPALETTEENTRY)palette); // Even if failure can realistically happen, consequences of using uninitialized palette seem acceptable.
  4080.         // Above: GetSystemPaletteEntries() is the only approach that provided the correct palette.
  4081.         // The following other approaches didn't give the right one:
  4082.         // GetDIBits(): The palette it stores in bmi.bmiColors seems completely wrong.
  4083.         // GetPaletteEntries()+GetCurrentObject(hdc, OBJ_PAL): Returned only 20 entries rather than the expected 256.
  4084.         // GetDIBColorTable(): I think same as above or maybe it returns 0.
  4085.  
  4086.         // The following section is necessary because apparently each new row in the region starts on
  4087.         // a DWORD boundary.  So if the number of pixels in each row isn't an exact multiple of 4, there
  4088.         // are between 1 and 3 zero-bytes at the end of each row.
  4089.         int remainder = aWidth % 4;
  4090.         int empty_bytes_at_end_of_each_row = remainder ? (4 - remainder) : 0;
  4091.  
  4092.         // Start at the last RGB slot and the last color index slot:
  4093.         BYTE *byte = (BYTE *)image_pixel + image_pixel_count - 1 + (aHeight * empty_bytes_at_end_of_each_row); // Pointer to 8-bit color indices.
  4094.         DWORD *pixel = image_pixel + image_pixel_count - 1; // Pointer to 32-bit RGB entries.
  4095.  
  4096.         int row, col;
  4097.         for (row = 0; row < aHeight; ++row) // For each row.
  4098.         {
  4099.             byte -= empty_bytes_at_end_of_each_row;
  4100.             for (col = 0; col < aWidth; ++col) // For each column.
  4101.                 *pixel-- = rgb_to_bgr(palette[*byte--]); // Caller always wants RGB vs. BGR format.
  4102.         }
  4103.     }
  4104.     
  4105.     // Since above didn't "goto end", indicate success:
  4106.     success = true;
  4107.  
  4108. end:
  4109.     if (tdc_orig_select) // i.e. the original call to SelectObject() didn't fail.
  4110.         SelectObject(tdc, tdc_orig_select); // Probably necessary to prevent memory leak.
  4111.     DeleteDC(tdc);
  4112.     if (!success && image_pixel)
  4113.     {
  4114.         free(image_pixel);
  4115.         image_pixel = NULL;
  4116.     }
  4117.     return image_pixel;
  4118. }
  4119.  
  4120.  
  4121.  
  4122. ResultType Line::PixelSearch(int aLeft, int aTop, int aRight, int aBottom, COLORREF aColorBGR
  4123.     , int aVariation, char *aOptions, bool aIsPixelGetColor)
  4124. // Caller has ensured that aColor is in BGR format unless caller passed true for aUseRGB, in which case
  4125. // it's in RGB format.
  4126. // Author: The fast-mode PixelSearch was created by Aurelian Maga.
  4127. {
  4128.     // For maintainability, get options and RGB/BGR conversion out of the way early.
  4129.     bool fast_mode = aIsPixelGetColor || strcasestr(aOptions, "Fast");
  4130.     bool use_rgb = strcasestr(aOptions, "RGB") != NULL;
  4131.     COLORREF aColorRGB;
  4132.     if (use_rgb) // aColorBGR currently contains an RGB value.
  4133.     {
  4134.         aColorRGB = aColorBGR;
  4135.         aColorBGR = rgb_to_bgr(aColorBGR);
  4136.     }
  4137.     else
  4138.         aColorRGB = rgb_to_bgr(aColorBGR); // rgb_to_bgr() also converts in the reverse direction, i.e. bgr_to_rgb().
  4139.  
  4140.     // Many of the following sections are similar to those in ImageSearch(), so they should be
  4141.     // maintained together.
  4142.  
  4143.     Var *output_var_x = ARGVAR1;  // Ok if NULL.
  4144.     Var *output_var_y = aIsPixelGetColor ? NULL : ARGVAR2;  // Ok if NULL. ARGVARRAW2 wouldn't be safe because load-time validation requires a min of only zero parameters to allow the output variables to be left blank.
  4145.  
  4146.     g_ErrorLevel->Assign(aIsPixelGetColor ? ERRORLEVEL_ERROR : ERRORLEVEL_ERROR2); // Set default ErrorLevel.  2 means error other than "color not found".
  4147.     if (output_var_x)
  4148.         output_var_x->Assign();  // Init to empty string regardless of whether we succeed here.
  4149.     if (output_var_y)
  4150.         output_var_y->Assign();  // Same.
  4151.  
  4152.     RECT rect = {0};
  4153.     if (!(g.CoordMode & COORD_MODE_PIXEL)) // Using relative vs. screen coordinates.
  4154.     {
  4155.         if (!GetWindowRect(GetForegroundWindow(), &rect))
  4156.             return OK;  // Let ErrorLevel tell the story.
  4157.         aLeft   += rect.left;
  4158.         aTop    += rect.top;
  4159.         aRight  += rect.left;  // Add left vs. right because we're adjusting based on the position of the window.
  4160.         aBottom += rect.top;   // Same.
  4161.     }
  4162.  
  4163.     if (aVariation < 0)
  4164.         aVariation = 0;
  4165.     if (aVariation > 255)
  4166.         aVariation = 255;
  4167.  
  4168.     // Allow colors to vary within the spectrum of intensity, rather than having them
  4169.     // wrap around (which doesn't seem to make much sense).  For example, if the user specified
  4170.     // a variation of 5, but the red component of aColorBGR is only 0x01, we don't want red_low to go
  4171.     // below zero, which would cause it to wrap around to a very intense red color:
  4172.     COLORREF pixel; // Used much further down.
  4173.     BYTE red, green, blue; // Used much further down.
  4174.     BYTE search_red, search_green, search_blue;
  4175.     BYTE red_low, green_low, blue_low, red_high, green_high, blue_high;
  4176.     if (aVariation > 0)
  4177.     {
  4178.         search_red = GetRValue(aColorBGR);
  4179.         search_green = GetGValue(aColorBGR);
  4180.         search_blue = GetBValue(aColorBGR);
  4181.     }
  4182.     //else leave uninitialized since they won't be used.
  4183.  
  4184.     HDC hdc = GetDC(NULL);
  4185.     if (!hdc)
  4186.         return OK;  // Let ErrorLevel tell the story.
  4187.  
  4188.     bool found = false; // Must init here for use by "goto fast_end" and for use by both fast and slow modes.
  4189.  
  4190.     if (fast_mode)
  4191.     {
  4192.         // From this point on, "goto fast_end" will assume hdc is non-NULL but that the below might still be NULL.
  4193.         // Therefore, all of the following must be initialized so that the "fast_end" label can detect them:
  4194.         HDC sdc = NULL;
  4195.         HBITMAP hbitmap_screen = NULL;
  4196.         LPCOLORREF screen_pixel = NULL;
  4197.         HGDIOBJ sdc_orig_select = NULL;
  4198.  
  4199.         // Some explanation for the method below is contained in this quote from the newsgroups:
  4200.         // "you shouldn't really be getting the current bitmap from the GetDC DC. This might
  4201.         // have weird effects like returning the entire screen or not working. Create yourself
  4202.         // a memory DC first of the correct size. Then BitBlt into it and then GetDIBits on
  4203.         // that instead. This way, the provider of the DC (the video driver) can make sure that
  4204.         // the correct pixels are copied across."
  4205.  
  4206.         // Create an empty bitmap to hold all the pixels currently visible on the screen (within the search area):
  4207.         int search_width = aRight - aLeft + 1;
  4208.         int search_height = aBottom - aTop + 1;
  4209.         if (   !(sdc = CreateCompatibleDC(hdc)) || !(hbitmap_screen = CreateCompatibleBitmap(hdc, search_width, search_height))   )
  4210.             goto fast_end;
  4211.  
  4212.         if (   !(sdc_orig_select = SelectObject(sdc, hbitmap_screen))   )
  4213.             goto fast_end;
  4214.  
  4215.         // Copy the pixels in the search-area of the screen into the DC to be searched:
  4216.         if (   !(BitBlt(sdc, 0, 0, search_width, search_height, hdc, aLeft, aTop, SRCCOPY))   )
  4217.             goto fast_end;
  4218.  
  4219.         LONG screen_width, screen_height;
  4220.         bool screen_is_16bit;
  4221.         if (   !(screen_pixel = getbits(hbitmap_screen, sdc, screen_width, screen_height, screen_is_16bit))   )
  4222.             goto fast_end;
  4223.  
  4224.         // Concerning 0xF8F8F8F8: "On 16bit and 15 bit color the first 5 bits in each byte are valid
  4225.         // (in 16bit there is an extra bit but i forgot for which color). And this will explain the
  4226.         // second problem [in the test script], since GetPixel even in 16bit will return some "valid"
  4227.         // data in the last 3bits of each byte."
  4228.         register int i;
  4229.         LONG screen_pixel_count = screen_width * screen_height;
  4230.         if (screen_is_16bit)
  4231.             for (i = 0; i < screen_pixel_count; ++i)
  4232.                 screen_pixel[i] &= 0xF8F8F8F8;
  4233.  
  4234.         if (aIsPixelGetColor)
  4235.         {
  4236.             COLORREF color = screen_pixel[0] & 0x00FFFFFF; // See other 0x00FFFFFF below for explanation.
  4237.             char buf[32];
  4238.             sprintf(buf, "0x%06X", use_rgb ? color : rgb_to_bgr(color));
  4239.             output_var_x->Assign(buf); // Caller has ensured that first output_var (x) won't be NULL in this mode.
  4240.             found = true; // ErrorLevel will be set to 0 further below.
  4241.         }
  4242.         else if (aVariation < 1) // Caller wants an exact match on one particular color.
  4243.         {
  4244.             if (screen_is_16bit)
  4245.                 aColorRGB &= 0xF8F8F8F8;
  4246.             for (i = 0; i < screen_pixel_count; ++i)
  4247.             {
  4248.                 // Note that screen pixels sometimes have a non-zero high-order byte.  That's why
  4249.                 // bit-and with 0x00FFFFFF is done.  Otherwise, Redish/orangish colors are not properly
  4250.                 // found:
  4251.                 if ((screen_pixel[i] & 0x00FFFFFF) == aColorRGB)
  4252.                 {
  4253.                     found = true;
  4254.                     break;
  4255.                 }
  4256.             }
  4257.         }
  4258.         else
  4259.         {
  4260.             // It seems more appropriate to do the 16-bit conversion prior to SET_COLOR_RANGE,
  4261.             // rather than applying 0xF8 to each of the high/low values individually.
  4262.             if (screen_is_16bit)
  4263.             {
  4264.                 search_red &= 0xF8;
  4265.                 search_green &= 0xF8;
  4266.                 search_blue &= 0xF8;
  4267.             }
  4268.  
  4269. #define SET_COLOR_RANGE \
  4270. {\
  4271.     red_low = (aVariation > search_red) ? 0 : search_red - aVariation;\
  4272.     green_low = (aVariation > search_green) ? 0 : search_green - aVariation;\
  4273.     blue_low = (aVariation > search_blue) ? 0 : search_blue - aVariation;\
  4274.     red_high = (aVariation > 0xFF - search_red) ? 0xFF : search_red + aVariation;\
  4275.     green_high = (aVariation > 0xFF - search_green) ? 0xFF : search_green + aVariation;\
  4276.     blue_high = (aVariation > 0xFF - search_blue) ? 0xFF : search_blue + aVariation;\
  4277. }
  4278.             
  4279.             SET_COLOR_RANGE
  4280.  
  4281.             for (i = 0; i < screen_pixel_count; ++i)
  4282.             {
  4283.                 // Note that screen pixels sometimes have a non-zero high-order byte.  But it doesn't
  4284.                 // matter with the below approach, since that byte is not checked in the comparison.
  4285.                 pixel = screen_pixel[i];
  4286.                 red = GetBValue(pixel);   // Because pixel is in RGB vs. BGR format, red is retrieved with
  4287.                 green = GetGValue(pixel); // GetBValue() and blue is retrieved with GetRValue().
  4288.                 blue = GetRValue(pixel);
  4289.                 if (red >= red_low && red <= red_high
  4290.                     && green >= green_low && green <= green_high
  4291.                     && blue >= blue_low && blue <= blue_high)
  4292.                 {
  4293.                     found = true;
  4294.                     break;
  4295.                 }
  4296.             }
  4297.         }
  4298.         if (!found) // Must override ErrorLevel to its new value prior to the label below.
  4299.             g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // "1" indicates search completed okay, but didn't find it.
  4300.  
  4301. fast_end:
  4302.         // If found==false when execution reaches here, ErrorLevel is already set to the right value, so just
  4303.         // clean up then return.
  4304.         ReleaseDC(NULL, hdc);
  4305.         if (sdc)
  4306.         {
  4307.             if (sdc_orig_select) // i.e. the original call to SelectObject() didn't fail.
  4308.                 SelectObject(sdc, sdc_orig_select); // Probably necessary to prevent memory leak.
  4309.             DeleteDC(sdc);
  4310.         }
  4311.         if (hbitmap_screen)
  4312.             DeleteObject(hbitmap_screen);
  4313.         if (screen_pixel)
  4314.             free(screen_pixel);
  4315.  
  4316.         if (!found) // Let ErrorLevel, which is either "1" or "2" as set earlier, tell the story.
  4317.             return OK;
  4318.  
  4319.         // Otherwise, success.  Calculate xpos and ypos of where the match was found and adjust
  4320.         // coords to make them relative to the position of the target window (rect will contain
  4321.         // zeroes if this doesn't need to be done):
  4322.         if (!aIsPixelGetColor)
  4323.         {
  4324.             if (output_var_x && !output_var_x->Assign((aLeft + i%screen_width) - rect.left))
  4325.                 return FAIL;
  4326.             if (output_var_y && !output_var_y->Assign((aTop + i/screen_width) - rect.top))
  4327.                 return FAIL;
  4328.         }
  4329.  
  4330.         return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  4331.     }
  4332.  
  4333.     // Otherwise (since above didn't return): fast_mode==false
  4334.     // This old/slower method is kept  because fast mode will break older scripts that rely on
  4335.     // which match is found if there is more than one match (since fast mode searches the
  4336.     // pixels in a different order (horizontally rather than verically, I believe).
  4337.     // In addition, there is doubt that the fast mode works in all the screen color depths, games,
  4338.     // and other circumstances that the slow mode is known to work in.
  4339.  
  4340.     // If the caller gives us inverted X or Y coordinates, conduct the search in reverse order.
  4341.     // This feature was requested; it was put into effect for v1.0.25.06.
  4342.     bool right_to_left = aLeft > aRight;
  4343.     bool bottom_to_top = aTop > aBottom;
  4344.     register int xpos, ypos;
  4345.  
  4346.     if (aVariation > 0)
  4347.         SET_COLOR_RANGE
  4348.  
  4349.     for (xpos = aLeft  // It starts at aLeft even if right_to_left is true.
  4350.         ; (right_to_left ? (xpos >= aRight) : (xpos <= aRight)) // Verified correct.
  4351.         ; xpos += right_to_left ? -1 : 1)
  4352.     {
  4353.         for (ypos = aTop  // It starts at aTop even if bottom_to_top is true.
  4354.             ; bottom_to_top ? (ypos >= aBottom) : (ypos <= aBottom) // Verified correct.
  4355.             ; ypos += bottom_to_top ? -1 : 1)
  4356.         {
  4357.             pixel = GetPixel(hdc, xpos, ypos); // Returns a BGR value, not RGB.
  4358.             if (aVariation < 1)  // User wanted an exact match.
  4359.             {
  4360.                 if (pixel == aColorBGR)
  4361.                 {
  4362.                     found = true;
  4363.                     break;
  4364.                 }
  4365.             }
  4366.             else  // User specified that some variation in each of the RGB components is allowable.
  4367.             {
  4368.                 red = GetRValue(pixel);
  4369.                 green = GetGValue(pixel);
  4370.                 blue = GetBValue(pixel);
  4371.                 if (red >= red_low && red <= red_high && green >= green_low && green <= green_high
  4372.                     && blue >= blue_low && blue <= blue_high)
  4373.                 {
  4374.                     found = true;
  4375.                     break;
  4376.                 }
  4377.             }
  4378.         }
  4379.         // Check this here rather than in the outer loop's top line because otherwise the loop's
  4380.         // increment would make xpos too big by 1:
  4381.         if (found)
  4382.             break;
  4383.     }
  4384.  
  4385.     ReleaseDC(NULL, hdc);
  4386.  
  4387.     if (!found)
  4388.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // This value indicates "color not found".
  4389.  
  4390.     // Otherwise, this pixel matches one of the specified color(s).
  4391.     // Adjust coords to make them relative to the position of the target window
  4392.     // (rect will contain zeroes if this doesn't need to be done):
  4393.     if (output_var_x && !output_var_x->Assign(xpos - rect.left))
  4394.         return FAIL;
  4395.     if (output_var_y && !output_var_y->Assign(ypos - rect.top))
  4396.         return FAIL;
  4397.     // Since above didn't return:
  4398.     return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  4399. }
  4400.  
  4401.  
  4402.  
  4403. ResultType Line::ImageSearch(int aLeft, int aTop, int aRight, int aBottom, char *aImageFile)
  4404. // Author: ImageSearch was created by Aurelian Maga.
  4405. {
  4406.     // Many of the following sections are similar to those in PixelSearch(), so they should be
  4407.     // maintained together.
  4408.     Var *output_var_x = ARGVAR1;  // Ok if NULL. RAW wouldn't be safe because load-time validation actually
  4409.     Var *output_var_y = ARGVAR2;  // requires a minimum of zero parameters so that the output-vars can be optional.
  4410.  
  4411.     // Set default results, both ErrorLevel and output variables, in case of early return:
  4412.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR2);  // 2 means error other than "image not found".
  4413.     if (output_var_x)
  4414.         output_var_x->Assign();  // Init to empty string regardless of whether we succeed here.
  4415.     if (output_var_y)
  4416.         output_var_y->Assign(); // Same.
  4417.  
  4418.     RECT rect = {0}; // Set default (for CoordMode == "screen").
  4419.     if (!(g.CoordMode & COORD_MODE_PIXEL)) // Using relative vs. screen coordinates.
  4420.     {
  4421.         if (!GetWindowRect(GetForegroundWindow(), &rect))
  4422.             return OK; // Let ErrorLevel tell the story.
  4423.         aLeft   += rect.left;
  4424.         aTop    += rect.top;
  4425.         aRight  += rect.left;  // Add left vs. right because we're adjusting based on the position of the window.
  4426.         aBottom += rect.top;   // Same.
  4427.     }
  4428.  
  4429.     // Options are done as asterisk+option to permit future expansion.
  4430.     // Set defaults to be possibly overridden by any specified options:
  4431.     int aVariation = 0;  // This is named aVariation vs. variation for use with the SET_COLOR_RANGE macro.
  4432.     COLORREF trans_color = CLR_NONE; // The default must be a value that can't occur naturally in an image.
  4433.     int icon_number = 0; // Zero means "load icon or bitmap (doesn't matter)".
  4434.     int width = 0, height = 0;
  4435.     // For icons, override the default to be 16x16 because that is what is sought 99% of the time.
  4436.     // This new default can be overridden by explicitly specifying w0 h0:
  4437.     char *cp = strrchr(aImageFile, '.');
  4438.     if (cp)
  4439.     {
  4440.         ++cp;
  4441.         if (!(stricmp(cp, "ico") && stricmp(cp, "exe") && stricmp(cp, "dll")))
  4442.             width = GetSystemMetrics(SM_CXSMICON), height = GetSystemMetrics(SM_CYSMICON);
  4443.     }
  4444.  
  4445.     char color_name[32], *dp;
  4446.     cp = omit_leading_whitespace(aImageFile); // But don't alter aImageFile yet in case it contains literal whitespace we want to retain.
  4447.     while (*cp == '*')
  4448.     {
  4449.         ++cp;
  4450.         switch (toupper(*cp))
  4451.         {
  4452.         case 'W': width = ATOI(cp + 1); break;
  4453.         case 'H': height = ATOI(cp + 1); break;
  4454.         default:
  4455.             if (!strnicmp(cp, "Icon", 4))
  4456.             {
  4457.                 cp += 4;  // Now it's the character after the word.
  4458.                 icon_number = ATOI(cp); // LoadPicture() correctly handles any negative value.
  4459.             }
  4460.             else if (!strnicmp(cp, "Trans", 5))
  4461.             {
  4462.                 cp += 5;  // Now it's the character after the word.
  4463.                 // Isolate the color name/number for ColorNameToBGR():
  4464.                 strlcpy(color_name, cp, sizeof(color_name));
  4465.                 if (dp = StrChrAny(color_name, " \t")) // Find space or tab, if any.
  4466.                     *dp = '\0';
  4467.                 // Fix for v1.0.44.10: Treat trans_color as containing an RGB value (not BGR) so that it matches
  4468.                 // the documented behavior.  In older versions, a specified color like "TransYellow" was wrong in
  4469.                 // every way (inverted) and a specified numeric color like "Trans0xFFFFAA" was treated as BGR vs. RGB.
  4470.                 trans_color = ColorNameToBGR(color_name);
  4471.                 if (trans_color == CLR_NONE) // A matching color name was not found, so assume it's in hex format.
  4472.                     // It seems strtol() automatically handles the optional leading "0x" if present:
  4473.                     trans_color = strtol(color_name, NULL, 16);
  4474.                     // if color_name did not contain something hex-numeric, black (0x00) will be assumed,
  4475.                     // which seems okay given how rare such a problem would be.
  4476.                 else
  4477.                     trans_color = bgr_to_rgb(trans_color); // v1.0.44.10: See fix/comment above.
  4478.  
  4479.             }
  4480.             else // Assume it's a number since that's the only other asterisk-option.
  4481.             {
  4482.                 aVariation = ATOI(cp); // Seems okay to support hex via ATOI because the space after the number is documented as being mandatory.
  4483.                 if (aVariation < 0)
  4484.                     aVariation = 0;
  4485.                 if (aVariation > 255)
  4486.                     aVariation = 255;
  4487.                 // Note: because it's possible for filenames to start with a space (even though Explorer itself
  4488.                 // won't let you create them that way), allow exactly one space between end of option and the
  4489.                 // filename itself:
  4490.             }
  4491.         } // switch()
  4492.         if (   !(cp = StrChrAny(cp, " \t"))   ) // Find the first space or tab after the option.
  4493.             return OK; // Bad option/format.  Let ErrorLevel tell the story.
  4494.         // Now it's the space or tab (if there is one) after the option letter.  Advance by exactly one character
  4495.         // because only one space or tab is considered the delimiter.  Any others are considered to be part of the
  4496.         // filename (though some or all OSes might simply ignore them or tolerate them as first-try match criteria).
  4497.         aImageFile = ++cp; // This should now point to another asterisk or the filename itself.
  4498.         // Above also serves to reset the filename to omit the option string whenever at least one asterisk-option is present.
  4499.         cp = omit_leading_whitespace(cp); // This is done to make it more tolerant of having more than one space/tab between options.
  4500.     }
  4501.  
  4502.     // Update: Transparency is now supported in icons by using the icon's mask.  In addition, an attempt
  4503.     // is made to support transparency in GIF, PNG, and possibly TIF files via the *Trans option, which
  4504.     // assumes that one color in the image is transparent.  In GIFs not loaded via GDIPlus, the transparent
  4505.     // color might always been seen as pure white, but when GDIPlus is used, it's probably always black
  4506.     // like it is in PNG -- however, this will not relied upon, at least not until confirmed.
  4507.     // OLDER/OBSOLETE comment kept for background:
  4508.     // For now, images that can't be loaded as bitmaps (icons and cursors) are not supported because most
  4509.     // icons have a transparent background or color present, which the image search routine here is
  4510.     // probably not equipped to handle (since the transparent color, when shown, typically reveals the
  4511.     // color of whatever is behind it; thus screen pixel color won't match image's pixel color).
  4512.     // So currently, only BMP and GIF seem to work reliably, though some of the other GDIPlus-supported
  4513.     // formats might work too.
  4514.     int image_type;
  4515.     HBITMAP hbitmap_image = LoadPicture(aImageFile, width, height, image_type, icon_number, false);
  4516.     // The comment marked OBSOLETE below is no longer true because the elimination of the high-byte via
  4517.     // 0x00FFFFFF seems to have fixed it.  But "true" is still not passed because that should increase
  4518.     // consistency when GIF/BMP/ICO files are used by a script on both Win9x and other OSs (since the
  4519.     // same loading method would be used via "false" for these formats across all OSes).
  4520.     // OBSOLETE: Must not pass "true" with the above because that causes bitmaps and gifs to be not found
  4521.     // by the search.  In other words, nothing works.  Obsolete comment: Pass "true" so that an attempt
  4522.     // will be made to load icons as bitmaps if GDIPlus is available.
  4523.     if (!hbitmap_image)
  4524.         return OK; // Let ErrorLevel tell the story.
  4525.  
  4526.     HDC hdc = GetDC(NULL);
  4527.     if (!hdc)
  4528.     {
  4529.         DeleteObject(hbitmap_image);
  4530.         return OK; // Let ErrorLevel tell the story.
  4531.     }
  4532.  
  4533.     // From this point on, "goto end" will assume hdc and hbitmap_image are non-NULL, but that the below
  4534.     // might still be NULL.  Therefore, all of the following must be initialized so that the "end"
  4535.     // label can detect them:
  4536.     HDC sdc = NULL;
  4537.     HBITMAP hbitmap_screen = NULL;
  4538.     LPCOLORREF image_pixel = NULL, screen_pixel = NULL, image_mask = NULL;
  4539.     HGDIOBJ sdc_orig_select = NULL;
  4540.     bool found = false; // Must init here for use by "goto end".
  4541.     
  4542.     bool image_is_16bit;
  4543.     LONG image_width, image_height;
  4544.  
  4545.     if (image_type == IMAGE_ICON)
  4546.     {
  4547.         // Must be done prior to IconToBitmap() since it deletes (HICON)hbitmap_image:
  4548.         ICONINFO ii;
  4549.         if (GetIconInfo((HICON)hbitmap_image, &ii))
  4550.         {
  4551.             // If the icon is monochrome (black and white), ii.hbmMask will contain twice as many pixels as
  4552.             // are actually in the icon.  But since the top half of the pixels are the AND-mask, it seems
  4553.             // okay to get all the pixels given the rarity of monochrome icons.  This scenario should be
  4554.             // handled properly because: 1) the variables image_height and image_width will be overridden
  4555.             // further below with the correct icon dimensions; 2) Only the first half of the pixels within
  4556.             // the image_mask array will actually be referenced by the transparency checker in the loops,
  4557.             // and that first half is the AND-mask, which is the transparency part that is needed.  The
  4558.             // second half, the XOR part, is not needed and thus ignored.  Also note that if width/height
  4559.             // required the icon to be scaled, LoadPicture() has already done that directly to the icon,
  4560.             // so ii.hbmMask should already be scaled to match the size of the bitmap created later below.
  4561.             image_mask = getbits(ii.hbmMask, hdc, image_width, image_height, image_is_16bit, 1);
  4562.             DeleteObject(ii.hbmColor); // DeleteObject() probably handles NULL okay since few MSDN/other examples ever check for NULL.
  4563.             DeleteObject(ii.hbmMask);
  4564.         }
  4565.         if (   !(hbitmap_image = IconToBitmap((HICON)hbitmap_image, true))   )
  4566.             return OK; // Let ErrorLevel tell the story.
  4567.     }
  4568.  
  4569.     if (   !(image_pixel = getbits(hbitmap_image, hdc, image_width, image_height, image_is_16bit))   )
  4570.         goto end;
  4571.  
  4572.     // Create an empty bitmap to hold all the pixels currently visible on the screen that lie within the search area:
  4573.     int search_width = aRight - aLeft + 1;
  4574.     int search_height = aBottom - aTop + 1;
  4575.     if (   !(sdc = CreateCompatibleDC(hdc)) || !(hbitmap_screen = CreateCompatibleBitmap(hdc, search_width, search_height))   )
  4576.         goto end;
  4577.  
  4578.     if (   !(sdc_orig_select = SelectObject(sdc, hbitmap_screen))   )
  4579.         goto end;
  4580.  
  4581.     // Copy the pixels in the search-area of the screen into the DC to be searched:
  4582.     if (   !(BitBlt(sdc, 0, 0, search_width, search_height, hdc, aLeft, aTop, SRCCOPY))   )
  4583.         goto end;
  4584.  
  4585.     LONG screen_width, screen_height;
  4586.     bool screen_is_16bit;
  4587.     if (   !(screen_pixel = getbits(hbitmap_screen, sdc, screen_width, screen_height, screen_is_16bit))   )
  4588.         goto end;
  4589.  
  4590.     LONG image_pixel_count = image_width * image_height;
  4591.     LONG screen_pixel_count = screen_width * screen_height;
  4592.     int i, j, k, x, y; // Declaring as "register" makes no performance difference with current compiler, so let the compiler choose which should be registers.
  4593.  
  4594.     // If either is 16-bit, convert *both* to the 16-bit-compatible 32-bit format:
  4595.     if (image_is_16bit || screen_is_16bit)
  4596.     {
  4597.         if (trans_color != CLR_NONE)
  4598.             trans_color &= 0x00F8F8F8; // Convert indicated trans-color to be compatible with the conversion below.
  4599.         for (i = 0; i < screen_pixel_count; ++i)
  4600.             screen_pixel[i] &= 0x00F8F8F8; // Highest order byte must be masked to zero for consistency with use of 0x00FFFFFF below.
  4601.         for (i = 0; i < image_pixel_count; ++i)
  4602.             image_pixel[i] &= 0x00F8F8F8;  // Same.
  4603.     }
  4604.  
  4605.     // v1.0.44.03: The below is now done even for variation>0 mode so its results are consistent with those of
  4606.     // non-variation mode.  This is relied upon by variation=0 mode but now also by the following line in the
  4607.     // variation>0 section:
  4608.     //     || image_pixel[j] == trans_color
  4609.     // Without this change, there are cases where variation=0 would find a match but a higher variation
  4610.     // (for the same search) wouldn't. 
  4611.     for (i = 0; i < image_pixel_count; ++i)
  4612.         image_pixel[i] &= 0x00FFFFFF;
  4613.  
  4614.     // Search the specified region for the first occurrence of the image:
  4615.     if (aVariation < 1) // Caller wants an exact match.
  4616.     {
  4617.         // Concerning the following use of 0x00FFFFFF, the use of 0x00F8F8F8 above is related (both have high order byte 00).
  4618.         // The following needs to be done only when shades-of-variation mode isn't in effect because
  4619.         // shades-of-variation mode ignores the high-order byte due to its use of macros such as GetRValue().
  4620.         // This transformation incurs about a 15% performance decrease (percentage is fairly constant since
  4621.         // it is proportional to the search-region size, which tends to be much larger than the search-image and
  4622.         // is therefore the primary determination of how long the loops take). But it definitely helps find images
  4623.         // more successfully in some cases.  For example, if a PNG file is displayed in a GUI window, this
  4624.         // transformation allows certain bitmap search-images to be found via variation==0 when they otherwise
  4625.         // would require variation==1 (possibly the variation==1 success is just a side-effect of it
  4626.         // ignoring the high-order byte -- maybe a much higher variation would be needed if the high
  4627.         // order byte were also subject to the same shades-of-variation analysis as the other three bytes [RGB]).
  4628.         for (i = 0; i < screen_pixel_count; ++i)
  4629.             screen_pixel[i] &= 0x00FFFFFF;
  4630.  
  4631.         for (i = 0; i < screen_pixel_count; ++i)
  4632.         {
  4633.             // Unlike the variation-loop, the following one uses a first-pixel optimization to boost performance
  4634.             // by about 10% because it's only 3 extra comparisons and exact-match mode is probably used more often.
  4635.             // Before even checking whether the other adjacent pixels in the region match the image, ensure
  4636.             // the image does not extend past the right or bottom edges of the current part of the search region.
  4637.             // This is done for performance but more importantly to prevent partial matches at the edges of the
  4638.             // search region from being considered complete matches.
  4639.             // The following check is ordered for short-circuit performance.  In addition, image_mask, if
  4640.             // non-NULL, is used to determine which pixels are transparent within the image and thus should
  4641.             // match any color on the screen.
  4642.             if ((screen_pixel[i] == image_pixel[0] // A screen pixel has been found that matches the image's first pixel.
  4643.                 || image_mask && image_mask[0]     // Or: It's an icon's transparent pixel, which matches any color.
  4644.                 || image_pixel[0] == trans_color)  // This should be okay even if trans_color==CLR_NONE, since CLR_NONE should never occur naturally in the image.
  4645.                 && image_height <= screen_height - i/screen_width // Image is short enough to fit in the remaining rows of the search region.
  4646.                 && image_width <= screen_width - i%screen_width)  // Image is narrow enough not to exceed the right-side boundary of the search region.
  4647.             {
  4648.                 // Check if this candidate region -- which is a subset of the search region whose height and width
  4649.                 // matches that of the image -- is a pixel-for-pixel match of the image.
  4650.                 for (found = true, x = 0, y = 0, j = 0, k = i; j < image_pixel_count; ++j)
  4651.                 {
  4652.                     if (!(found = (screen_pixel[k] == image_pixel[j] // At least one pixel doesn't match, so this candidate is discarded.
  4653.                         || image_mask && image_mask[j]      // Or: It's an icon's transparent pixel, which matches any color.
  4654.                         || image_pixel[j] == trans_color))) // This should be okay even if trans_color==CLR_NONE, since CLR none should never occur naturally in the image.
  4655.                         break;
  4656.                     if (++x < image_width) // We're still within the same row of the image, so just move on to the next screen pixel.
  4657.                         ++k;
  4658.                     else // We're starting a new row of the image.
  4659.                     {
  4660.                         x = 0; // Return to the leftmost column of the image.
  4661.                         ++y;   // Move one row downward in the image.
  4662.                         // Move to the next row within the current-candiate region (not the entire search region).
  4663.                         // This is done by moving vertically downward from "i" (which is the upper-left pixel of the
  4664.                         // current-candidate region) by "y" rows.
  4665.                         k = i + y*screen_width; // Verified correct.
  4666.                     }
  4667.                 }
  4668.                 if (found) // Complete match found.
  4669.                     break;
  4670.             }
  4671.         }
  4672.     }
  4673.     else // Allow colors to vary by aVariation shades; i.e. approximate match is okay.
  4674.     {
  4675.         // The following section is part of the first-pixel-check optimization that improves performance by
  4676.         // 15% or more depending on where and whether a match is found.  This section and one the follows
  4677.         // later is commented out to reduce code size.
  4678.         // Set high/low range for the first pixel of the image since it is the pixel most often checked
  4679.         // (i.e. for performance).
  4680.         //BYTE search_red1 = GetBValue(image_pixel[0]);  // Because it's RGB vs. BGR, the B value is fetched, not R (though it doesn't matter as long as everything is internally consistent here).
  4681.         //BYTE search_green1 = GetGValue(image_pixel[0]);
  4682.         //BYTE search_blue1 = GetRValue(image_pixel[0]); // Same comment as above.
  4683.         //BYTE red_low1 = (aVariation > search_red1) ? 0 : search_red1 - aVariation;
  4684.         //BYTE green_low1 = (aVariation > search_green1) ? 0 : search_green1 - aVariation;
  4685.         //BYTE blue_low1 = (aVariation > search_blue1) ? 0 : search_blue1 - aVariation;
  4686.         //BYTE red_high1 = (aVariation > 0xFF - search_red1) ? 0xFF : search_red1 + aVariation;
  4687.         //BYTE green_high1 = (aVariation > 0xFF - search_green1) ? 0xFF : search_green1 + aVariation;
  4688.         //BYTE blue_high1 = (aVariation > 0xFF - search_blue1) ? 0xFF : search_blue1 + aVariation;
  4689.         // Above relies on the fact that the 16-bit conversion higher above was already done because like
  4690.         // in PixelSearch, it seems more appropriate to do the 16-bit conversion prior to setting the range
  4691.         // of high and low colors (vs. than applying 0xF8 to each of the high/low values individually).
  4692.  
  4693.         BYTE red, green, blue;
  4694.         BYTE search_red, search_green, search_blue;
  4695.         BYTE red_low, green_low, blue_low, red_high, green_high, blue_high;
  4696.  
  4697.         // The following loop is very similar to its counterpart above that finds an exact match, so maintain
  4698.         // them together and see above for more detailed comments about it.
  4699.         for (i = 0; i < screen_pixel_count; ++i)
  4700.         {
  4701.             // The following is commented out to trade code size reduction for performance (see comment above).
  4702.             //red = GetBValue(screen_pixel[i]);   // Because it's RGB vs. BGR, the B value is fetched, not R (though it doesn't matter as long as everything is internally consistent here).
  4703.             //green = GetGValue(screen_pixel[i]);
  4704.             //blue = GetRValue(screen_pixel[i]);
  4705.             //if ((red >= red_low1 && red <= red_high1
  4706.             //    && green >= green_low1 && green <= green_high1
  4707.             //    && blue >= blue_low1 && blue <= blue_high1 // All three color components are a match, so this screen pixel matches the image's first pixel.
  4708.             //        || image_mask && image_mask[0]         // Or: It's an icon's transparent pixel, which matches any color.
  4709.             //        || image_pixel[0] == trans_color)      // This should be okay even if trans_color==CLR_NONE, since CLR none should never occur naturally in the image.
  4710.             //    && image_height <= screen_height - i/screen_width // Image is short enough to fit in the remaining rows of the search region.
  4711.             //    && image_width <= screen_width - i%screen_width)  // Image is narrow enough not to exceed the right-side boundary of the search region.
  4712.             
  4713.             // Instead of the above, only this abbreviated check is done:
  4714.             if (image_height <= screen_height - i/screen_width    // Image is short enough to fit in the remaining rows of the search region.
  4715.                 && image_width <= screen_width - i%screen_width)  // Image is narrow enough not to exceed the right-side boundary of the search region.
  4716.             {
  4717.                 // Since the first pixel is a match, check the other pixels.
  4718.                 for (found = true, x = 0, y = 0, j = 0, k = i; j < image_pixel_count; ++j)
  4719.                 {
  4720.                        search_red = GetBValue(image_pixel[j]);
  4721.                        search_green = GetGValue(image_pixel[j]);
  4722.                        search_blue = GetRValue(image_pixel[j]);
  4723.                     SET_COLOR_RANGE
  4724.                        red = GetBValue(screen_pixel[k]);
  4725.                        green = GetGValue(screen_pixel[k]);
  4726.                        blue = GetRValue(screen_pixel[k]);
  4727.  
  4728.                     if (!(found = red >= red_low && red <= red_high
  4729.                         && green >= green_low && green <= green_high
  4730.                         && blue >= blue_low && blue <= blue_high
  4731.                             || image_mask && image_mask[j]     // Or: It's an icon's transparent pixel, which matches any color.
  4732.                             || image_pixel[j] == trans_color)) // This should be okay even if trans_color==CLR_NONE, since CLR_NONE should never occur naturally in the image.
  4733.                         break; // At least one pixel doesn't match, so this candidate is discarded.
  4734.                     if (++x < image_width) // We're still within the same row of the image, so just move on to the next screen pixel.
  4735.                         ++k;
  4736.                     else // We're starting a new row of the image.
  4737.                     {
  4738.                         x = 0; // Return to the leftmost column of the image.
  4739.                         ++y;   // Move one row downward in the image.
  4740.                         k = i + y*screen_width; // Verified correct.
  4741.                     }
  4742.                 }
  4743.                 if (found) // Complete match found.
  4744.                     break;
  4745.             }
  4746.         }
  4747.     }
  4748.  
  4749.     if (!found) // Must override ErrorLevel to its new value prior to the label below.
  4750.         g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // "1" indicates search completed okay, but didn't find it.
  4751.  
  4752. end:
  4753.     // If found==false when execution reaches here, ErrorLevel is already set to the right value, so just
  4754.     // clean up then return.
  4755.     ReleaseDC(NULL, hdc);
  4756.     DeleteObject(hbitmap_image);
  4757.     if (sdc)
  4758.     {
  4759.         if (sdc_orig_select) // i.e. the original call to SelectObject() didn't fail.
  4760.             SelectObject(sdc, sdc_orig_select); // Probably necessary to prevent memory leak.
  4761.         DeleteDC(sdc);
  4762.     }
  4763.     if (hbitmap_screen)
  4764.         DeleteObject(hbitmap_screen);
  4765.     if (image_pixel)
  4766.         free(image_pixel);
  4767.     if (image_mask)
  4768.         free(image_mask);
  4769.     if (screen_pixel)
  4770.         free(screen_pixel);
  4771.  
  4772.     if (!found) // Let ErrorLevel, which is either "1" or "2" as set earlier, tell the story.
  4773.         return OK;
  4774.  
  4775.     // Otherwise, success.  Calculate xpos and ypos of where the match was found and adjust
  4776.     // coords to make them relative to the position of the target window (rect will contain
  4777.     // zeroes if this doesn't need to be done):
  4778.     if (output_var_x && !output_var_x->Assign((aLeft + i%screen_width) - rect.left))
  4779.         return FAIL;
  4780.     if (output_var_y && !output_var_y->Assign((aTop + i/screen_width) - rect.top))
  4781.         return FAIL;
  4782.  
  4783.     return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  4784. }
  4785.  
  4786.  
  4787.  
  4788. /////////////////
  4789. // Main Window //
  4790. /////////////////
  4791.  
  4792. LRESULT CALLBACK MainWindowProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
  4793. {
  4794.     int i;
  4795.     DWORD dwTemp;
  4796.  
  4797.     // Detect Explorer crashes so that tray icon can be recreated.  I think this only works on Win98
  4798.     // and beyond, since the feature was never properly implemented in Win95:
  4799.     static UINT WM_TASKBARCREATED = RegisterWindowMessage("TaskbarCreated");
  4800.  
  4801.     // See GuiWindowProc() for details about this first section:
  4802.     LRESULT msg_reply;
  4803.     if (g_MsgMonitorCount // Count is checked here to avoid function-call overhead.
  4804.         && (!g.CalledByIsDialogMessageOrDispatch || g.CalledByIsDialogMessageOrDispatchMsg != iMsg) // v1.0.44.11: If called by IsDialog or Dispatch but they changed the message number, check if the script is monitoring that new number.
  4805.         && MsgMonitor(hWnd, iMsg, wParam, lParam, NULL, msg_reply))
  4806.         return msg_reply; // MsgMonitor has returned "true", indicating that this message should be omitted from further processing.
  4807.     g.CalledByIsDialogMessageOrDispatch = false; // v1.0.40.01.
  4808.  
  4809.     TRANSLATE_AHK_MSG(iMsg, wParam)
  4810.     
  4811.     switch (iMsg)
  4812.     {
  4813.     case WM_COMMAND:
  4814.         if (HandleMenuItem(hWnd, LOWORD(wParam), -1)) // It was handled fully. -1 flags it as a non-GUI menu item such as a tray menu or popup menu.
  4815.             return 0; // If an application processes this message, it should return zero.
  4816.         break; // Otherwise, let DefWindowProc() try to handle it (this actually seems to happen normally sometimes).
  4817.  
  4818.     case AHK_NOTIFYICON:  // Tray icon clicked on.
  4819.     {
  4820.         switch(lParam)
  4821.         {
  4822. // Don't allow the main window to be opened this way by a compiled EXE, since it will display
  4823. // the lines most recently executed, and many people who compile scripts don't want their users
  4824. // to see the contents of the script:
  4825.         case WM_LBUTTONDOWN:
  4826.             if (g_script.mTrayMenu->mClickCount != 1) // Activating tray menu's default item requires double-click.
  4827.                 break; // Let default proc handle it (since that's what used to happen, it seems safest).
  4828.             //else fall through to the next case.
  4829.         case WM_LBUTTONDBLCLK:
  4830.             if (g_script.mTrayMenu->mDefault)
  4831.                 POST_AHK_USER_MENU(hWnd, g_script.mTrayMenu->mDefault->mMenuID, -1) // -1 flags it as a non-GUI menu item.
  4832. #ifdef AUTOHOTKEYSC
  4833.             else if (g_script.mTrayMenu->mIncludeStandardItems && g_AllowMainWindow)
  4834.                 ShowMainWindow();
  4835.             // else do nothing.
  4836. #else
  4837.             else if (g_script.mTrayMenu->mIncludeStandardItems)
  4838.                 ShowMainWindow();
  4839.             // else do nothing.
  4840. #endif
  4841.             return 0;
  4842.         case WM_RBUTTONUP:
  4843.             // v1.0.30.03:
  4844.             // Opening the menu upon UP vs. DOWN solves at least one set of problems: The fact that
  4845.             // when the right mouse button is remapped as shown in the example below, it prevents
  4846.             // the left button from being able to select a menu item from the tray menu.  It might
  4847.             // solve other problems also, and it seems fairly common for other apps to open the
  4848.             // menu upon UP rather than down.  Even Explorer's own context menus are like this.
  4849.             // The following example is trivial and serves only to illustrate the problem caused
  4850.             // by the old open-tray-on-mouse-down method:
  4851.             //MButton::Send {RButton down}
  4852.             //MButton up::Send {RButton up}
  4853.             g_script.mTrayMenu->Display(false);
  4854.             return 0;
  4855.         } // Inner switch()
  4856.         break;
  4857.     } // case AHK_NOTIFYICON
  4858.  
  4859.     case AHK_DIALOG:  // User defined msg sent from our functions MsgBox() or FileSelectFile().
  4860.     {
  4861.         // Always call this to close the clipboard if it was open (e.g. due to a script
  4862.         // line such as "MsgBox, %clipboard%" that got us here).  Seems better just to
  4863.         // do this rather than incurring the delay and overhead of a MsgSleep() call:
  4864.         CLOSE_CLIPBOARD_IF_OPEN;
  4865.         
  4866.         // Ensure that the app's top-most window (the modal dialog) is the system's
  4867.         // foreground window.  This doesn't use FindWindow() since it can hang in rare
  4868.         // cases.  And GetActiveWindow, GetTopWindow, GetWindow, etc. don't seem appropriate.
  4869.         // So EnumWindows is probably the way to do it:
  4870.         HWND top_box = FindOurTopDialog();
  4871.         if (top_box)
  4872.         {
  4873.  
  4874.             // v1.0.33: The following is probably reliable since the AHK_DIALOG should
  4875.             // be in front of any messages that would launch an interrupting thread.  In other
  4876.             // words, the "g" struct should still be the one that owns this MsgBox/dialog window.
  4877.             g.DialogHWND = top_box; // This is used to work around an AHK_TIMEOUT issue in which a MsgBox that has only an OK button fails to deliver the Timeout indicator to the script.
  4878.  
  4879.             SetForegroundWindowEx(top_box);
  4880.  
  4881.             // Setting the big icon makes AutoHotkey dialogs more distinct in the Alt-tab menu.
  4882.             // Unfortunately, it seems that setting the big icon also indirectly sets the small
  4883.             // icon, or more precisely, that the dialog simply scales the large icon whenever
  4884.             // a small one isn't available.  This results in the FileSelectFile dialog's title
  4885.             // being initially messed up (at least on WinXP) and also puts an unwanted icon in
  4886.             // the title bar of each MsgBox.  So for now it's disabled:
  4887.             //LPARAM main_icon = (LPARAM)LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_MAIN), IMAGE_ICON, 0, 0, LR_SHARED);
  4888.             //SendMessage(top_box, WM_SETICON, ICON_BIG, main_icon);
  4889.             //SendMessage(top_box, WM_SETICON, ICON_SMALL, 0);  // Tried this to get rid of it, but it didn't help.
  4890.             // But don't set the small one, because it reduces the area available for title text
  4891.             // without adding any significant benefit:
  4892.             //SendMessage(top_box, WM_SETICON, ICON_SMALL, main_icon);
  4893.  
  4894.             UINT timeout = (UINT)lParam;  // Caller has ensured that this is non-negative.
  4895.             if (timeout)
  4896.                 // Caller told us to establish a timeout for this modal dialog (currently always MessageBox).
  4897.                 // In addition to any other reasons, the first param of the below must not be NULL because
  4898.                 // that would cause the 2nd param to be ignored.  We want the 2nd param to be the actual
  4899.                 // ID assigned to this timer.
  4900.                 SetTimer(top_box, g_nMessageBoxes, (UINT)timeout, MsgBoxTimeout);
  4901.         }
  4902.         // else: if !top_box: no error reporting currently.
  4903.         return 0;
  4904.     }
  4905.  
  4906.     case AHK_USER_MENU:
  4907.         // Search for AHK_USER_MENU in GuiWindowProc() for comments about why this is done:
  4908.         PostMessage(hWnd, iMsg, wParam, lParam);
  4909.         MsgSleep(-1, RETURN_AFTER_MESSAGES_SPECIAL_FILTER);
  4910.         return 0;
  4911.  
  4912.     case WM_HOTKEY: // As a result of this app having previously called RegisterHotkey().
  4913.     case AHK_HOOK_HOTKEY:  // Sent from this app's keyboard or mouse hook.
  4914.     case AHK_HOTSTRING: // Added for v1.0.36.02 so that hotstrings work even while an InputBox or other non-standard msg pump is running.
  4915.     case AHK_CLIPBOARD_CHANGE: // Added for v1.0.44 so that clipboard notifications aren't lost while the script is displaying a MsgBox or other dialog.
  4916.         // If the following facts are ever confirmed, there would be no need to post the message in cases where
  4917.         // the MsgSleep() won't be done:
  4918.         // 1) The mere fact that any of the above messages has been received here in MainWindowProc means that a
  4919.         //    message pump other than our own main one is running (i.e. it is the closest pump on the call stack).
  4920.         //    This is because our main message pump would never have dispatched the types of messages above because
  4921.         //    it is designed to fully handle then discard them.
  4922.         // 2) All of these types of non-main message pumps would discard a message with a NULL hwnd.
  4923.         //
  4924.         // One source of confusion is that there are quite a few different types of message pumps that might
  4925.         // be running:
  4926.         // - InputBox/MsgBox, or other dialog
  4927.         // - Popup menu (tray menu, popup menu from Menu command, or context menu of an Edit/MonthCal, including
  4928.         //   our main window's edit control g_hWndEdit).
  4929.         // - Probably others, such as ListView marquee-drag, that should be listed here as they are
  4930.         //   remembered/discovered.
  4931.         //
  4932.         // Due to maintainability and the uncertainty over backward compatibility (see comments above), the
  4933.         // following message is posted even when INTERRUPTIBLE==false.
  4934.         // Post it with a NULL hwnd (update: also for backward compatibility) to avoid any chance that our
  4935.         // message pump will dispatch it back to us.  We want these events to always be handled there,
  4936.         // where almost all new quasi-threads get launched.  Update: Even if it were safe in terms of
  4937.         // backward compatibility to change NULL to gHwnd, testing shows it causes problems when a hotkey
  4938.         // is pressed while one of the script's menus is displayed (at least a menu bar).  For example:
  4939.         // *LCtrl::Send {Blind}{Ctrl up}{Alt down}
  4940.         // *LCtrl up::Send {Blind}{Alt up}
  4941.         PostMessage(NULL, iMsg, wParam, lParam);
  4942.         if (INTERRUPTIBLE)
  4943.             MsgSleep(-1, RETURN_AFTER_MESSAGES_SPECIAL_FILTER);
  4944.         //else let the other pump discard this hotkey event since in most cases it would do more harm than good
  4945.         // (see comments above for why the message is posted even when it is 90% certain it will be discarded
  4946.         // in all cases where MsgSleep isn't done).
  4947.         return 0;
  4948.  
  4949.     case WM_TIMER:
  4950.         // MSDN: "When you specify a TimerProc callback function, the default window procedure calls
  4951.         // the callback function when it processes WM_TIMER. Therefore, you need to dispatch messages
  4952.         // in the calling thread, even when you use TimerProc instead of processing WM_TIMER."
  4953.         // MSDN CONTRADICTION: "You can process the message by providing a WM_TIMER case in the window
  4954.         // procedure. Otherwise, DispatchMessage will call the TimerProc callback function specified in
  4955.         // the call to the SetTimer function used to install the timer."
  4956.         // In light of the above, it seems best to let the default proc handle this message if it
  4957.         // has a non-NULL lparam:
  4958.         if (lParam)
  4959.             break;
  4960.         // Otherwise, it's the main timer, which is the means by which joystick hotkeys and script timers
  4961.         // created via the script command "SetTimer" continue to execute even while a dialog's message pump
  4962.         // is running.  Even if the script is NOT INTERRUPTIBLE (which generally isn't possible, since
  4963.         // the mere fact that we're here means that a dialog's message pump dispatched a message to us
  4964.         // [since our msg pump would not dispatch this type of msg], which in turn means that the script
  4965.         // should be interruptible due to DIALOG_PREP), call MsgSleep() anyway so that joystick
  4966.         // hotkeys will be polled.  If any such hotkeys are "newly down" right now, those events queued
  4967.         // will be buffered/queued for later, when the script becomes interruptible again.  Also, don't
  4968.         // call CheckScriptTimers() or PollJoysticks() directly from here.  See comments at the top of
  4969.         // those functions for why.
  4970.         // This is an older comment, but I think it might still apply, which is why MsgSleep() is not
  4971.         // called when a popup menu or a window's main menu is visible.  We don't really want to run the
  4972.         // script's timed subroutines or monitor joystick hotkeys while a menu is displayed anyway:
  4973.         // Do not call MsgSleep() while a popup menu is visible because that causes long delays
  4974.         // sometime when the user is trying to select a menu (the user's click is ignored and the menu
  4975.         // stays visible).  I think this is because MsgSleep()'s PeekMessage() intercepts the user's
  4976.         // clicks and is unable to route them to TrackPopupMenuEx()'s message loop, which is the only
  4977.         // place they can be properly processed.  UPDATE: This also needs to be done when the MAIN MENU
  4978.         // is visible, because testing shows that that menu would otherwise become sluggish too, perhaps
  4979.         // more rarely, when timers are running.
  4980.         // Other background info:
  4981.         // Checking g_MenuIsVisible here prevents timed subroutines from running while the tray menu
  4982.         // or main menu is in use.  This is documented behavior, and is desirable most of the time
  4983.         // anyway.  But not to do this would produce strange effects because any timed subroutine
  4984.         // that took a long time to run might keep us away from the "menu loop", which would result
  4985.         // in the menu becoming temporarily unresponsive while the user is in it (and probably other
  4986.         // undesired effects).
  4987.         if (!g_MenuIsVisible)
  4988.             MsgSleep(-1, RETURN_AFTER_MESSAGES_SPECIAL_FILTER);
  4989.         return 0;
  4990.  
  4991.     case WM_SYSCOMMAND:
  4992.         if ((wParam == SC_CLOSE || wParam == SC_MINIMIZE) && hWnd == g_hWnd) // i.e. behave this way only for main window.
  4993.         {
  4994.             // The user has either clicked the window's "X" button, chosen "Close"
  4995.             // from the system (upper-left icon) menu, or pressed Alt-F4.  In all
  4996.             // these cases, we want to hide the window rather than actually closing
  4997.             // it.  If the user really wishes to exit the program, a File->Exit
  4998.             // menu option may be available, or use the Tray Icon, or launch another
  4999.             // instance which will close the previous, etc.  UPDATE: SC_MINIMIZE is
  5000.             // now handled this way also so that owned windows (such as Splash and
  5001.             // Progress) won't be hidden when the main window is hidden.
  5002.             ShowWindow(g_hWnd, SW_HIDE);
  5003.             return 0;
  5004.         }
  5005.         break;
  5006.  
  5007.     case WM_CLOSE:
  5008.         if (hWnd == g_hWnd) // i.e. not the SplashText window or anything other than the main.
  5009.         {
  5010.             // Receiving this msg is fairly unusual since SC_CLOSE is intercepted and redefined above.
  5011.             // However, it does happen if an external app is asking us to close, such as another
  5012.             // instance of this same script during the Reload command.  So treat it in a way similar
  5013.             // to the user having chosen Exit from the menu.
  5014.             //
  5015.             // Leave it up to ExitApp() to decide whether to terminate based upon whether
  5016.             // there is an OnExit subroutine, whether that subroutine is already running at
  5017.             // the time a new WM_CLOSE is received, etc.  It's also its responsibility to call
  5018.             // DestroyWindow() upon termination so that the WM_DESTROY message winds up being
  5019.             // received and process in this function (which is probably necessary for a clean
  5020.             // termination of the app and all its windows):
  5021.             g_script.ExitApp(EXIT_WM_CLOSE);
  5022.             return 0;  // Verified correct.
  5023.         }
  5024.         // Otherwise, some window of ours other than our main window was destroyed
  5025.         // (perhaps the splash window).  Let DefWindowProc() handle it:
  5026.         break;
  5027.  
  5028.     case WM_ENDSESSION: // MSDN: "A window receives this message through its WindowProc function."
  5029.         if (wParam) // The session is being ended.
  5030.             g_script.ExitApp((lParam & ENDSESSION_LOGOFF) ? EXIT_LOGOFF : EXIT_SHUTDOWN);
  5031.         //else a prior WM_QUERYENDSESSION was aborted; i.e. the session really isn't ending.
  5032.         return 0;  // Verified correct.
  5033.  
  5034.     case AHK_EXIT_BY_RELOAD:
  5035.         g_script.ExitApp(EXIT_RELOAD);
  5036.         return 0; // Whether ExitApp() terminates depends on whether there's an OnExit subroutine and what it does.
  5037.  
  5038.     case AHK_EXIT_BY_SINGLEINSTANCE:
  5039.         g_script.ExitApp(EXIT_SINGLEINSTANCE);
  5040.         return 0; // Whether ExitApp() terminates depends on whether there's an OnExit subroutine and what it does.
  5041.  
  5042.     case WM_DESTROY:
  5043.         if (hWnd == g_hWnd) // i.e. not the SplashText window or anything other than the main.
  5044.         {
  5045.             if (!g_DestroyWindowCalled)
  5046.                 // This is done because I believe it's possible for a WM_DESTROY message to be received
  5047.                 // even though we didn't call DestroyWindow() ourselves (e.g. via DefWindowProc() receiving
  5048.                 // and acting upon a WM_CLOSE or us calling DestroyWindow() directly) -- perhaps the window
  5049.                 // is being forcibly closed or something else abnormal happened.  Make a best effort to run
  5050.                 // the OnExit subroutine, if present, even without a main window (testing on an earlier
  5051.                 // versions shows that most commands work fine without the window). Pass the empty string
  5052.                 // to tell it to terminate after running the OnExit subroutine:
  5053.                 g_script.ExitApp(EXIT_DESTROY, "");
  5054.             // Do not do PostQuitMessage() here because we don't know the proper exit code.
  5055.             // MSDN: "The exit value returned to the system must be the wParam parameter of
  5056.             // the WM_QUIT message."
  5057.             // If we're here, it means our thread called DestroyWindow() directly or indirectly
  5058.             // (currently, it's only called directly).  By returning, our thread should resume
  5059.             // execution at the statement after DestroyWindow() in whichever caller called that:
  5060.             return 0;  // "If an application processes this message, it should return zero."
  5061.         }
  5062.         // Otherwise, some window of ours other than our main window was destroyed
  5063.         // (perhaps the splash window).  Let DefWindowProc() handle it:
  5064.         break;
  5065.  
  5066.     case WM_CREATE:
  5067.         // MSDN: If an application processes this message, it should return zero to continue
  5068.         // creation of the window. If the application returns 1, the window is destroyed and
  5069.         // the CreateWindowEx or CreateWindow function returns a NULL handle.
  5070.         return 0;
  5071.  
  5072.     case WM_ERASEBKGND:
  5073.     case WM_CTLCOLORSTATIC:
  5074.     case WM_PAINT:
  5075.     case WM_SIZE:
  5076.     {
  5077.         if (iMsg == WM_SIZE)
  5078.         {
  5079.             if (hWnd == g_hWnd)
  5080.             {
  5081.                 if (wParam == SIZE_MINIMIZED)
  5082.                     // Minimizing the main window hides it.
  5083.                     ShowWindow(g_hWnd, SW_HIDE);
  5084.                 else
  5085.                     MoveWindow(g_hWndEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
  5086.                 return 0; // The correct return value for this msg.
  5087.             }
  5088.             if (hWnd == g_hWndSplash || wParam == SIZE_MINIMIZED)
  5089.                 break;  // Let DefWindowProc() handle it for splash window and Progress windows.
  5090.         }
  5091.         else
  5092.             if (hWnd == g_hWnd || hWnd == g_hWndSplash)
  5093.                 break; // Let DWP handle it.
  5094.  
  5095.         for (i = 0; i < MAX_SPLASHIMAGE_WINDOWS; ++i)
  5096.             if (g_SplashImage[i].hwnd == hWnd)
  5097.                 break;
  5098.         bool is_splashimage = (i < MAX_SPLASHIMAGE_WINDOWS);
  5099.         if (!is_splashimage)
  5100.         {
  5101.             for (i = 0; i < MAX_PROGRESS_WINDOWS; ++i)
  5102.                 if (g_Progress[i].hwnd == hWnd)
  5103.                     break;
  5104.             if (i == MAX_PROGRESS_WINDOWS) // It's not a progress window either.
  5105.                 // Let DefWindowProc() handle it (should probably never happen since currently the only
  5106.                 // other type of window is SplashText, which never receive this msg?)
  5107.                 break;
  5108.         }
  5109.  
  5110.         SplashType &splash = is_splashimage ? g_SplashImage[i] : g_Progress[i];
  5111.         RECT client_rect;
  5112.  
  5113.         switch (iMsg)
  5114.         {
  5115.         case WM_SIZE:
  5116.         {
  5117.             // Allow any width/height to be specified so that the window can be "rolled up" to its title bar.
  5118.             int new_width = LOWORD(lParam);
  5119.             int new_height = HIWORD(lParam);
  5120.             if (new_width != splash.width || new_height != splash.height)
  5121.             {
  5122.                 GetClientRect(splash.hwnd, &client_rect);
  5123.                 int control_width = client_rect.right - (splash.margin_x * 2);
  5124.                 SPLASH_CALC_YPOS
  5125.                 // The Y offset for each control should match those used in Splash():
  5126.                 if (new_width != splash.width)
  5127.                 {
  5128.                     if (splash.hwnd_text1) // This control doesn't exist if the main text was originally blank.
  5129.                         MoveWindow(splash.hwnd_text1, PROGRESS_MAIN_POS, FALSE);
  5130.                     if (splash.hwnd_bar)
  5131.                         MoveWindow(splash.hwnd_bar, PROGRESS_BAR_POS, FALSE);
  5132.                     splash.width = new_width;
  5133.                 }
  5134.                 // Move the window EVEN IF new_height == splash.height because otherwise the text won't
  5135.                 // get re-centered when only the width of the window changes:
  5136.                 MoveWindow(splash.hwnd_text2, PROGRESS_SUB_POS, FALSE); // Negative height seems handled okay.
  5137.                 // Specifying true for "repaint" in MoveWindow() is not always enough refresh the text correctly,
  5138.                 // so this is done instead:
  5139.                 InvalidateRect(splash.hwnd, &client_rect, TRUE);
  5140.                 // If the user resizes the window, have that size retained (remembered) until the script
  5141.                 // explicitly changes it or the script destroys the window.
  5142.                 splash.height = new_height;
  5143.             }
  5144.             return 0;  // i.e. completely handled here.
  5145.         }
  5146.         case WM_CTLCOLORSTATIC:
  5147.             if (!splash.hbrush && splash.color_text == CLR_DEFAULT) // Let DWP handle it.
  5148.                 break;
  5149.             // Since we're processing this msg and not DWP, must set background color unconditionally,
  5150.             // otherwise plain white will likely be used:
  5151.             SetBkColor((HDC)wParam, splash.hbrush ? splash.color_bk : GetSysColor(COLOR_BTNFACE));
  5152.             if (splash.color_text != CLR_DEFAULT)
  5153.                 SetTextColor((HDC)wParam, splash.color_text);
  5154.             // Always return a real HBRUSH so that Windows knows we altered the HDC for it to use:
  5155.             return (LRESULT)(splash.hbrush ? splash.hbrush : GetSysColorBrush(COLOR_BTNFACE));
  5156.         case WM_ERASEBKGND:
  5157.         {
  5158.             if (splash.pic) // And since there is a pic, its object_width/height should already be valid.
  5159.             {
  5160.                 // MSDN: get width and height of picture
  5161.                 long hm_width, hm_height;
  5162.                 splash.pic->get_Width(&hm_width);
  5163.                 splash.pic->get_Height(&hm_height);
  5164.                 GetClientRect(splash.hwnd, &client_rect);
  5165.                 // MSDN: display picture using IPicture::Render
  5166.                 int ypos = splash.margin_y + (splash.text1_height ? (splash.text1_height + splash.margin_y) : 0);
  5167.                 splash.pic->Render((HDC)wParam, splash.margin_x, ypos, splash.object_width, splash.object_height
  5168.                     , 0, hm_height, hm_width, -hm_height, &client_rect);
  5169.                 // Prevent "flashing" by erasing only the part that hasn't already been drawn:
  5170.                 ExcludeClipRect((HDC)wParam, splash.margin_x, ypos, splash.margin_x + splash.object_width
  5171.                     , ypos + splash.object_height);
  5172.                 HRGN hrgn = CreateRectRgn(0, 0, 1, 1);
  5173.                 GetClipRgn((HDC)wParam, hrgn);
  5174.                 FillRgn((HDC)wParam, hrgn, splash.hbrush ? splash.hbrush : GetSysColorBrush(COLOR_BTNFACE));
  5175.                 DeleteObject(hrgn);
  5176.                 return 1; // "An application should return nonzero if it erases the background."
  5177.             }
  5178.             // Otherwise, it's a Progress window (or a SplashImage window with no picture):
  5179.             if (!splash.hbrush) // Let DWP handle it.
  5180.                 break;
  5181.             RECT clipbox;
  5182.             GetClipBox((HDC)wParam, &clipbox);
  5183.             FillRect((HDC)wParam, &clipbox, splash.hbrush);
  5184.             return 1; // "An application should return nonzero if it erases the background."
  5185.         }
  5186.         } // switch()
  5187.         break; // Let DWP handle it.
  5188.     }
  5189.         
  5190.     case WM_SETFOCUS:
  5191.         if (hWnd == g_hWnd)
  5192.         {
  5193.             SetFocus(g_hWndEdit);  // Always focus the edit window, since it's the only navigable control.
  5194.             return 0;
  5195.         }
  5196.         break;
  5197.  
  5198.     case WM_DRAWCLIPBOARD:
  5199.         if (g_script.mOnClipboardChangeLabel) // In case it's a bogus msg, it's our responsibility to avoid posting the msg if there's no label to launch.
  5200.             PostMessage(g_hWnd, AHK_CLIPBOARD_CHANGE, 0, 0); // It's done this way to buffer it when the script is uninterruptible, etc.  v1.0.44: Post to g_hWnd vs. NULL so that notifications aren't lost when script is displaying a MsgBox or other dialog.
  5201.         if (g_script.mNextClipboardViewer) // Will be NULL if there are no other windows in the chain.
  5202.             SendMessageTimeout(g_script.mNextClipboardViewer, iMsg, wParam, lParam, SMTO_ABORTIFHUNG, 2000, &dwTemp);
  5203.         return 0;
  5204.  
  5205.     case WM_CHANGECBCHAIN:
  5206.         // MSDN: If the next window is closing, repair the chain. 
  5207.         if ((HWND)wParam == g_script.mNextClipboardViewer)
  5208.             g_script.mNextClipboardViewer = (HWND)lParam;
  5209.         // MSDN: Otherwise, pass the message to the next link. 
  5210.         else if (g_script.mNextClipboardViewer)
  5211.             SendMessageTimeout(g_script.mNextClipboardViewer, iMsg, wParam, lParam, SMTO_ABORTIFHUNG, 2000, &dwTemp);
  5212.         return 0;
  5213.  
  5214.     case AHK_GETWINDOWTEXT:
  5215.         // It's best to handle this msg here rather than in the main event loop in case a non-standard message
  5216.         // pump is running (such as MsgBox's), in which case this msg would be dispatched directly here.
  5217.         if (IsWindow((HWND)lParam)) // In case window has been destroyed since msg was posted.
  5218.             GetWindowText((HWND)lParam, (char *)wParam, KEY_HISTORY_WINDOW_TITLE_SIZE);
  5219.         // Probably best not to do the following because it could result in such "low priority" messages
  5220.         // getting out of step with each other, and could also introduce KeyHistory WinTitle "lag":
  5221.         // Could give low priority to AHK_GETWINDOWTEXT under the theory that sometimes the call takes a long
  5222.         // time to return: Upon receipt of such a message, repost it whenever Peek(specific_msg_range, PM_NOREMOVE)
  5223.         // detects a thread-starting event on the queue.  However, Peek might be a high overhead call in some cases,
  5224.         // such as when/if it yields our timeslice upon returning FALSE (uncertain/unlikely, but in any case
  5225.         // it might do more harm than good).
  5226.         return 0;
  5227.  
  5228.     case AHK_RETURN_PID:
  5229.         // This is obsolete in light of WinGet's support for fetching the PID of any window.
  5230.         // But since it's simple, it is retained for backward compatibility.
  5231.         // Rajat wanted this so that it's possible to discover the PID based on the title of each
  5232.         // script's main window (i.e. if there are multiple scripts running).  Also note that this
  5233.         // msg can be sent via TRANSLATE_AHK_MSG() to prevent it from ever being filtered out (and
  5234.         // thus delayed) while the script is uninterruptible.  For example:
  5235.         // SendMessage, 0x44, 1029,,, %A_ScriptFullPath% - AutoHotkey
  5236.         // SendMessage, 1029,,,, %A_ScriptFullPath% - AutoHotkey  ; Same as above but not sent via TRANSLATE.
  5237.         return GetCurrentProcessId(); // Don't use ReplyMessage because then our thread can't reply to itself with this answer.
  5238.  
  5239.     HANDLE_MENU_LOOP // Cases for WM_ENTERMENULOOP and WM_EXITMENULOOP.
  5240.  
  5241.     default:
  5242.         // The following iMsg can't be in the switch() since it's not constant:
  5243.         if (iMsg == WM_TASKBARCREATED && !g_NoTrayIcon) // !g_NoTrayIcon --> the tray icon should be always visible.
  5244.         {
  5245.             g_script.CreateTrayIcon();
  5246.             g_script.UpdateTrayIcon(true);  // Force the icon into the correct pause, suspend, or mIconFrozen state.
  5247.             // And now pass this iMsg on to DefWindowProc() in case it does anything with it.
  5248.         }
  5249.  
  5250.     } // switch()
  5251.  
  5252.     return DefWindowProc(hWnd, iMsg, wParam, lParam);
  5253. }
  5254.  
  5255.  
  5256.  
  5257. bool HandleMenuItem(HWND aHwnd, WORD aMenuItemID, WPARAM aGuiIndex)
  5258. // See if an item was selected from the tray menu or main menu.  Note that it is possible
  5259. // for one of the standard menu items to be triggered from a GUI menu if the menu or one of
  5260. // its submenus was modified with the "menu, MenuName, Standard" command.
  5261. // Returns true if the message is fully handled here, false otherwise.
  5262. {
  5263.     char buf_temp[2048];  // For various uses.
  5264.  
  5265.     switch (aMenuItemID)
  5266.     {
  5267.     case ID_TRAY_OPEN:
  5268.         ShowMainWindow();
  5269.         return true;
  5270.     case ID_TRAY_EDITSCRIPT:
  5271.     case ID_FILE_EDITSCRIPT:
  5272.         g_script.Edit();
  5273.         return true;
  5274.     case ID_TRAY_RELOADSCRIPT:
  5275.     case ID_FILE_RELOADSCRIPT:
  5276.         if (!g_script.Reload(false))
  5277.             MsgBox("The script could not be reloaded.");
  5278.         return true;
  5279.     case ID_TRAY_WINDOWSPY:
  5280.     case ID_FILE_WINDOWSPY:
  5281.         // ActionExec()'s CreateProcess() is currently done in a way that prefers enclosing double quotes:
  5282.         *buf_temp = '"';
  5283.         // Try GetAHKInstallDir() first so that compiled scripts running on machines that happen
  5284.         // to have AHK installed will still be able to fetch the help file:
  5285.         if (GetAHKInstallDir(buf_temp + 1))
  5286.             snprintfcat(buf_temp, sizeof(buf_temp), "\\AU3_Spy.exe\"");
  5287.         else
  5288.             // Mostly this ELSE is here in case AHK isn't installed (i.e. the user just
  5289.             // copied the files over without running the installer).  But also:
  5290.             // Even if this is the self-contained version (AUTOHOTKEYSC), attempt to launch anyway in
  5291.             // case the user has put a copy of WindowSpy in the same dir with the compiled script:
  5292.             // ActionExec()'s CreateProcess() is currently done in a way that prefers enclosing double quotes:
  5293.             snprintfcat(buf_temp, sizeof(buf_temp), "%sAU3_Spy.exe\"", g_script.mOurEXEDir);
  5294.         if (!g_script.ActionExec(buf_temp, "", NULL, false))
  5295.             MsgBox(buf_temp, 0, "Could not launch Window Spy:");
  5296.         return true;
  5297.     case ID_TRAY_HELP:
  5298.     case ID_HELP_USERMANUAL:
  5299.         // ActionExec()'s CreateProcess() is currently done in a way that prefers enclosing double quotes:
  5300.         *buf_temp = '"';
  5301.         // Try GetAHKInstallDir() first so that compiled scripts running on machines that happen
  5302.         // to have AHK installed will still be able to fetch the help file:
  5303.         if (GetAHKInstallDir(buf_temp + 1))
  5304.             snprintfcat(buf_temp, sizeof(buf_temp), "\\AutoHotkey.chm\"");
  5305.         else
  5306.             // Even if this is the self-contained version (AUTOHOTKEYSC), attempt to launch anyway in
  5307.             // case the user has put a copy of the help file in the same dir with the compiled script:
  5308.             // Also, for this one I saw it report failure once on Win98SE even though the help file did
  5309.             // wind up getting launched.  Couldn't repeat it.  So in reponse to that try explicit "hh.exe":
  5310.             snprintfcat(buf_temp, sizeof(buf_temp), "%sAutoHotkey.chm\"", g_script.mOurEXEDir);
  5311.         if (!g_script.ActionExec("hh.exe", buf_temp, NULL, false))
  5312.         {
  5313.             // Try it without the hh.exe in case .CHM is associate with some other application
  5314.             // in some OSes:
  5315.             if (!g_script.ActionExec(buf_temp, "", NULL, false)) // Use "" vs. NULL to specify that there are no params at all.
  5316.                 MsgBox(buf_temp, 0, "Could not launch help file:");
  5317.         }
  5318.         return true;
  5319.     case ID_TRAY_SUSPEND:
  5320.     case ID_FILE_SUSPEND:
  5321.         Line::ToggleSuspendState();
  5322.         return true;
  5323.     case ID_TRAY_PAUSE:
  5324.     case ID_FILE_PAUSE:
  5325.         if (g_nThreads > 0) // v1.0.37.06: Pausing the "idle thread" (which is not included in the thread count) is an easy means by which to disable all timers.
  5326.         {
  5327.             if (g.IsPaused)
  5328.                 --g_nPausedThreads;
  5329.             else
  5330.                 ++g_nPausedThreads;
  5331.         }
  5332.         else // Toggle the pause state of the idle thread.
  5333.             g_IdleIsPaused = !g_IdleIsPaused;
  5334.         g.IsPaused = !g.IsPaused;
  5335.         g_script.UpdateTrayIcon();
  5336.         CheckMenuItem(GetMenu(g_hWnd), ID_FILE_PAUSE, g.IsPaused ? MF_CHECKED : MF_UNCHECKED);
  5337.         return true;
  5338.     case ID_TRAY_EXIT:
  5339.     case ID_FILE_EXIT:
  5340.         g_script.ExitApp(EXIT_MENU);  // More reliable than PostQuitMessage(), which has been known to fail in rare cases.
  5341.         return true; // If there is an OnExit subroutine, the above might not actually exit.
  5342.     case ID_VIEW_LINES:
  5343.         ShowMainWindow(MAIN_MODE_LINES);
  5344.         return true;
  5345.     case ID_VIEW_VARIABLES:
  5346.         ShowMainWindow(MAIN_MODE_VARS);
  5347.         return true;
  5348.     case ID_VIEW_HOTKEYS:
  5349.         ShowMainWindow(MAIN_MODE_HOTKEYS);
  5350.         return true;
  5351.     case ID_VIEW_KEYHISTORY:
  5352.         ShowMainWindow(MAIN_MODE_KEYHISTORY);
  5353.         return true;
  5354.     case ID_VIEW_REFRESH:
  5355.         ShowMainWindow(MAIN_MODE_REFRESH);
  5356.         return true;
  5357.     case ID_HELP_WEBSITE:
  5358.         if (!g_script.ActionExec("http://www.autohotkey.com", "", NULL, false))
  5359.             MsgBox("Could not open URL http://www.autohotkey.com in default browser.");
  5360.         return true;
  5361.     default:
  5362.         // See if this command ID is one of the user's custom menu items.  Due to the possibility
  5363.         // that some items have been deleted from the menu, can't rely on comparing
  5364.         // aMenuItemID to g_script.mMenuItemCount in any way.  Just look up the ID to make sure
  5365.         // there really is a menu item for it:
  5366.         if (!g_script.FindMenuItemByID(aMenuItemID)) // Do nothing, let caller try to handle it some other way.
  5367.             return false;
  5368.         // It seems best to treat the selection of a custom menu item in a way similar
  5369.         // to how hotkeys are handled by the hook. See comments near the definition of
  5370.         // POST_AHK_USER_MENU for more details.
  5371.         POST_AHK_USER_MENU(aHwnd, aMenuItemID, aGuiIndex) // Send the menu's cmd ID and the window index (index is safer than pointer, since pointer might get deleted).
  5372.         // Try to maintain a list here of all the ways the script can be uninterruptible
  5373.         // at this moment in time, and whether that uninterruptibility should be overridden here:
  5374.         // 1) YES: g_MenuIsVisible is true (which in turn means that the script is marked
  5375.         //    uninterruptible to prevent timed subroutines from running and possibly
  5376.         //    interfering with menu navigation): Seems impossible because apparently 
  5377.         //    the WM_RBUTTONDOWN must first be returned from before we're called directly
  5378.         //    with the WM_COMMAND message corresponding to the menu item chosen by the user.
  5379.         //    In other words, g_MenuIsVisible will be false and the script thus will
  5380.         //    not be uninterruptible, at least not solely for that reason.
  5381.         // 2) YES: A new hotkey or timed subroutine was just launched and it's still in its
  5382.         //    grace period.  In this case, ExecUntil()'s call of PeekMessage() every 10ms
  5383.         //    or so will catch the item we just posted.  But it seems okay to interrupt
  5384.         //    here directly in most such cases.  InitNewThread(): Newly launched
  5385.         //    timed subroutine or hotkey subroutine.
  5386.         // 3) YES: Script is engaged in an uninterruptible activity such as SendKeys().  In this
  5387.         //    case, since the user has managed to get the tray menu open, it's probably
  5388.         //    best to process the menu item with the same priority as if any other menu
  5389.         //    item had been selected, interrupting even a critical operation since that's
  5390.         //    probably what the user would want.  SLEEP_WITHOUT_INTERRUPTION: SendKeys,
  5391.         //    Mouse input, Clipboard open, SetForegroundWindowEx().
  5392.         // 4) YES: AutoExecSection(): Since its grace period is only 100ms, doesn't seem to be
  5393.         //    a problem.  In any case, the timer would fire and briefly interrupt the menu
  5394.         //    subroutine we're trying to launch here even if a menu item were somehow
  5395.         //    activated in the first 100ms.
  5396.         //
  5397.         // IN LIGHT OF THE ABOVE, it seems best not to do the below.  In addition, the msg
  5398.         // filtering done by MsgSleep when the script is uninterruptible now excludes the
  5399.         // AHK_USER_MENU message (i.e. that message is always retrieved and acted upon,
  5400.         // even when the script is uninterruptible):
  5401.         //if (!INTERRUPTIBLE)
  5402.         //    return true;  // Leave the message buffered until later.
  5403.         // Now call the main loop to handle the message we just posted (and any others):
  5404.         return true;
  5405.     } // switch()
  5406.     return false;  // Indicate that the message was NOT handled.
  5407. }
  5408.  
  5409.  
  5410.  
  5411. ResultType ShowMainWindow(MainWindowModes aMode, bool aRestricted)
  5412. // Always returns OK for caller convenience.
  5413. {
  5414.     // v1.0.30.05: Increased from 32 KB to 64 KB, which is the maximum size of an Edit
  5415.     // in Win9x:
  5416.     char buf_temp[65534] = "";  // Formerly 32767.
  5417.     bool jump_to_bottom = false;  // Set default behavior for edit control.
  5418.     static MainWindowModes current_mode = MAIN_MODE_NO_CHANGE;
  5419.  
  5420. #ifdef AUTOHOTKEYSC
  5421.     // If we were called from a restricted place, such as via the Tray Menu or the Main Menu,
  5422.     // don't allow potentially sensitive info such as script lines and variables to be shown.
  5423.     // This is done so that scripts can be compiled more securely, making it difficult for anyone
  5424.     // to use ListLines to see the author's source code.  Rather than make exceptions for things
  5425.     // like KeyHistory, it seems best to forbit all information reporting except in cases where
  5426.     // existing info in the main window -- which must have gotten their via an allowed command
  5427.     // such as ListLines encountered in the script -- is being refreshed.  This is because in
  5428.     // that case, the script author has given de facto permission for that loophole (and it's
  5429.     // a pretty small one, not easy to exploit):
  5430.     if (aRestricted && !g_AllowMainWindow && (current_mode == MAIN_MODE_NO_CHANGE || aMode != MAIN_MODE_REFRESH))
  5431.     {
  5432.         SendMessage(g_hWndEdit, WM_SETTEXT, 0, (LPARAM)
  5433.             "Script info will not be shown because the \"Menu, Tray, MainWindow\"\r\n"
  5434.             "command option was not enabled in the original script.");
  5435.         return OK;
  5436.     }
  5437. #endif
  5438.  
  5439.     // If the window is empty, caller wants us to default it to showing the most recently
  5440.     // executed script lines:
  5441.     if (current_mode == MAIN_MODE_NO_CHANGE && (aMode == MAIN_MODE_NO_CHANGE || aMode == MAIN_MODE_REFRESH))
  5442.         aMode = MAIN_MODE_LINES;
  5443.  
  5444.     switch (aMode)
  5445.     {
  5446.     // case MAIN_MODE_NO_CHANGE: do nothing
  5447.     case MAIN_MODE_LINES:
  5448.         Line::LogToText(buf_temp, sizeof(buf_temp));
  5449.         jump_to_bottom = true;
  5450.         break;
  5451.     case MAIN_MODE_VARS:
  5452.         g_script.ListVars(buf_temp, sizeof(buf_temp));
  5453.         break;
  5454.     case MAIN_MODE_HOTKEYS:
  5455.         Hotkey::ListHotkeys(buf_temp, sizeof(buf_temp));
  5456.         break;
  5457.     case MAIN_MODE_KEYHISTORY:
  5458.         g_script.ListKeyHistory(buf_temp, sizeof(buf_temp));
  5459.         break;
  5460.     case MAIN_MODE_REFRESH:
  5461.         // Rather than do a recursive call to self, which might stress the stack if the script is heavily recursed:
  5462.         switch (current_mode)
  5463.         {
  5464.         case MAIN_MODE_LINES:
  5465.             Line::LogToText(buf_temp, sizeof(buf_temp));
  5466.             jump_to_bottom = true;
  5467.             break;
  5468.         case MAIN_MODE_VARS:
  5469.             g_script.ListVars(buf_temp, sizeof(buf_temp));
  5470.             break;
  5471.         case MAIN_MODE_HOTKEYS:
  5472.             Hotkey::ListHotkeys(buf_temp, sizeof(buf_temp));
  5473.             break;
  5474.         case MAIN_MODE_KEYHISTORY:
  5475.             g_script.ListKeyHistory(buf_temp, sizeof(buf_temp));
  5476.             // Special mode for when user refreshes, so that new keys can be seen without having
  5477.             // to scroll down again:
  5478.             jump_to_bottom = true;
  5479.             break;
  5480.         }
  5481.         break;
  5482.     }
  5483.  
  5484.     if (aMode != MAIN_MODE_REFRESH && aMode != MAIN_MODE_NO_CHANGE)
  5485.         current_mode = aMode;
  5486.  
  5487.     // Update the text before displaying the window, since it might be a little less disruptive
  5488.     // and might also be quicker if the window is hidden or non-foreground.
  5489.     // Unlike SetWindowText(), this method seems to expand tab characters:
  5490.     if (aMode != MAIN_MODE_NO_CHANGE)
  5491.         SendMessage(g_hWndEdit, WM_SETTEXT, 0, (LPARAM)buf_temp);
  5492.  
  5493.     if (!IsWindowVisible(g_hWnd))
  5494.     {
  5495.         ShowWindow(g_hWnd, SW_SHOW);
  5496.         if (IsIconic(g_hWnd)) // This happens whenver the window was last hidden via the minimize button.
  5497.             ShowWindow(g_hWnd, SW_RESTORE);
  5498.     }
  5499.     if (g_hWnd != GetForegroundWindow())
  5500.         if (!SetForegroundWindow(g_hWnd))
  5501.             SetForegroundWindowEx(g_hWnd);  // Only as a last resort, since it uses AttachThreadInput()
  5502.  
  5503.     if (jump_to_bottom)
  5504.     {
  5505.         SendMessage(g_hWndEdit, EM_LINESCROLL , 0, 999999);
  5506.         //SendMessage(g_hWndEdit, EM_SETSEL, -1, -1);
  5507.         //SendMessage(g_hWndEdit, EM_SCROLLCARET, 0, 0);
  5508.     }
  5509.     return OK;
  5510. }
  5511.  
  5512.  
  5513.  
  5514. DWORD GetAHKInstallDir(char *aBuf)
  5515. // Caller must ensure that aBuf is large enough (either by having called this function a previous time
  5516. // to get the length, or by making it MAX_PATH in capacity).
  5517. // Returns the length of the string (0 if empty).
  5518. {
  5519.     char buf[MAX_PATH];
  5520.     VarSizeType length = ReadRegString(HKEY_LOCAL_MACHINE, "SOFTWARE\\AutoHotkey", "InstallDir", buf, MAX_PATH);
  5521.     if (aBuf)
  5522.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string).
  5523.     return length;
  5524. }
  5525.  
  5526.  
  5527.  
  5528. //////////////
  5529. // InputBox //
  5530. //////////////
  5531.  
  5532. ResultType InputBox(Var *aOutputVar, char *aTitle, char *aText, bool aHideInput, int aWidth, int aHeight
  5533.     , int aX, int aY, double aTimeout, char *aDefault)
  5534. {
  5535.     // Note: for maximum compatibility with existing AutoIt2 scripts, do not
  5536.     // set ErrorLevel to ERRORLEVEL_ERROR when the user presses cancel.  Instead,
  5537.     // just set the output var to be blank.
  5538.     if (g_nInputBoxes >= MAX_INPUTBOXES)
  5539.     {
  5540.         // Have a maximum to help prevent runaway hotkeys due to key-repeat feature, etc.
  5541.         MsgBox("The maximum number of InputBoxes has been reached." ERR_ABORT);
  5542.         return FAIL;
  5543.     }
  5544.     if (!aOutputVar) return FAIL;
  5545.     if (!*aTitle)
  5546.         // If available, the script's filename seems a much better title in case the user has
  5547.         // more than one script running:
  5548.         aTitle = (g_script.mFileName && *g_script.mFileName) ? g_script.mFileName : NAME_PV;
  5549.     // Limit the size of what we were given to prevent unreasonably huge strings from
  5550.     // possibly causing a failure in CreateDialog().  This copying method is always done because:
  5551.     // Make a copy of all string parameters, using the stack, because they may reside in the deref buffer
  5552.     // and other commands (such as those in timed/hotkey subroutines) maybe overwrite the deref buffer.
  5553.     // This is not strictly necessary since InputBoxProc() is called immediately and makes instantaneous
  5554.     // and one-time use of these strings (not needing them after that), but it feels safer:
  5555.     char title[DIALOG_TITLE_SIZE];
  5556.     char text[4096];  // Size was increased in light of the fact that dialog can be made larger now.
  5557.     char default_string[4096];
  5558.     strlcpy(title, aTitle, sizeof(title));
  5559.     strlcpy(text, aText, sizeof(text));
  5560.     strlcpy(default_string, aDefault, sizeof(default_string));
  5561.     g_InputBox[g_nInputBoxes].title = title;
  5562.     g_InputBox[g_nInputBoxes].text = text;
  5563.     g_InputBox[g_nInputBoxes].default_string = default_string;
  5564.  
  5565.     if (aTimeout > 2147483) // This is approximately the max number of seconds that SetTimer() can handle.
  5566.         aTimeout = 2147483;
  5567.     if (aTimeout < 0) // But it can be equal to zero to indicate no timeout at all.
  5568.         aTimeout = 0.1;  // A value that might cue the user that something is wrong.
  5569.     g_InputBox[g_nInputBoxes].timeout = (DWORD)(aTimeout * 1000);  // Convert to ms
  5570.  
  5571.     // Allow 0 width or height (hides the window):
  5572.     g_InputBox[g_nInputBoxes].width = aWidth != INPUTBOX_DEFAULT && aWidth < 0 ? 0 : aWidth;
  5573.     g_InputBox[g_nInputBoxes].height = aHeight != INPUTBOX_DEFAULT && aHeight < 0 ? 0 : aHeight;
  5574.     g_InputBox[g_nInputBoxes].xpos = aX;  // But seems okay to allow these to be negative, even if absolute coords.
  5575.     g_InputBox[g_nInputBoxes].ypos = aY;
  5576.     g_InputBox[g_nInputBoxes].output_var = aOutputVar;
  5577.     g_InputBox[g_nInputBoxes].password_char = aHideInput ? '*' : '\0';
  5578.  
  5579.     // At this point, we know a dialog will be displayed.  See macro's comments for details:
  5580.     DIALOG_PREP
  5581.  
  5582.     // Specify NULL as the owner window since we want to be able to have the main window in the foreground even
  5583.     // if there are InputBox windows.  Update: A GUI window can now be the parent if thread has that setting.
  5584.     ++g_nInputBoxes;
  5585.     int result = (int)DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_INPUTBOX), THREAD_DIALOG_OWNER, InputBoxProc);
  5586.     --g_nInputBoxes;
  5587.  
  5588.     DIALOG_END
  5589.  
  5590.     // See the comments in InputBoxProc() for why ErrorLevel is set here rather than there.
  5591.     switch(result)
  5592.     {
  5593.     case AHK_TIMEOUT:
  5594.         // In this case the TimerProc already set the output variable to be what the user
  5595.         // entered.  Set ErrorLevel (in this case) even for AutoIt2 scripts since the script
  5596.         // is explicitly using a new feature:
  5597.         return g_ErrorLevel->Assign("2");
  5598.     case IDOK:
  5599.     case IDCANCEL:
  5600.         // For AutoIt2 (.aut) scripts:
  5601.         // If the user pressed the cancel button, InputBoxProc() set the output variable to be blank so
  5602.         // that there is a way to detect that the cancel button was pressed.  This is because
  5603.         // the InputBox command does not set ErrorLevel for .aut scripts (to maximize backward
  5604.         // compatibility), except when the command times out (see the help file for details).
  5605.         // For non-AutoIt2 scripts: The output variable is set to whatever the user entered,
  5606.         // even if the user pressed the cancel button.  This allows the cancel button to specify
  5607.         // that a different operation should be performed on the entered text:
  5608.         if (!g_script.mIsAutoIt2)
  5609.             return g_ErrorLevel->Assign(result == IDCANCEL ? ERRORLEVEL_ERROR : ERRORLEVEL_NONE);
  5610.         // else don't change the value of ErrorLevel at all to retain compatibility.
  5611.         break;
  5612.     case -1:
  5613.         MsgBox("The InputBox window could not be displayed.");
  5614.         // No need to set ErrorLevel since this is a runtime error that will kill the current quasi-thread.
  5615.         return FAIL;
  5616.     case FAIL:
  5617.         return FAIL;
  5618.     }
  5619.  
  5620.     return OK;
  5621. }
  5622.  
  5623.  
  5624.  
  5625. BOOL CALLBACK InputBoxProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
  5626. // MSDN:
  5627. // Typically, the dialog box procedure should return TRUE if it processed the message,
  5628. // and FALSE if it did not. If the dialog box procedure returns FALSE, the dialog
  5629. // manager performs the default dialog operation in response to the message.
  5630. {
  5631.     // See GuiWindowProc() for details about this first part:
  5632.     LRESULT msg_reply;
  5633.     if (g_MsgMonitorCount // Count is checked here to avoid function-call overhead.
  5634.         && (!g.CalledByIsDialogMessageOrDispatch || g.CalledByIsDialogMessageOrDispatchMsg != uMsg) // v1.0.44.11: If called by IsDialog or Dispatch but they changed the message number, check if the script is monitoring that new number.
  5635.         && MsgMonitor(hWndDlg, uMsg, wParam, lParam, NULL, msg_reply))
  5636.         return (BOOL)msg_reply; // MsgMonitor has returned "true", indicating that this message should be omitted from further processing.
  5637.     g.CalledByIsDialogMessageOrDispatch = false; // v1.0.40.01.
  5638.  
  5639.     HWND hControl;
  5640.  
  5641.     // Set default array index for g_InputBox[].  Caller has ensured that g_nInputBoxes > 0:
  5642.     int target_index = g_nInputBoxes - 1;
  5643.     #define CURR_INPUTBOX g_InputBox[target_index]
  5644.  
  5645.     switch(uMsg)
  5646.     {
  5647.     case WM_INITDIALOG:
  5648.     {
  5649.         // Clipboard may be open if its contents were used to build the text or title
  5650.         // of this dialog (e.g. "InputBox, out, %clipboard%").  It's best to do this before
  5651.         // anything that might take a relatively long time (e.g. SetForegroundWindowEx()):
  5652.         CLOSE_CLIPBOARD_IF_OPEN;
  5653.  
  5654.         CURR_INPUTBOX.hwnd = hWndDlg;
  5655.  
  5656.         if (CURR_INPUTBOX.password_char)
  5657.             SendDlgItemMessage(hWndDlg, IDC_INPUTEDIT, EM_SETPASSWORDCHAR, CURR_INPUTBOX.password_char, 0);
  5658.  
  5659.         SetWindowText(hWndDlg, CURR_INPUTBOX.title);
  5660.         if (hControl = GetDlgItem(hWndDlg, IDC_INPUTPROMPT))
  5661.             SetWindowText(hControl, CURR_INPUTBOX.text);
  5662.  
  5663.         // Don't do this check; instead allow the MoveWindow() to occur unconditionally so that
  5664.         // the new button positions and such will override those set in the dialog's resource
  5665.         // properties:
  5666.         //if (CURR_INPUTBOX.width != INPUTBOX_DEFAULT || CURR_INPUTBOX.height != INPUTBOX_DEFAULT
  5667.         //    || CURR_INPUTBOX.xpos != INPUTBOX_DEFAULT || CURR_INPUTBOX.ypos != INPUTBOX_DEFAULT)
  5668.         RECT rect;
  5669.         GetWindowRect(hWndDlg, &rect);
  5670.         int new_width = (CURR_INPUTBOX.width == INPUTBOX_DEFAULT) ? rect.right - rect.left : CURR_INPUTBOX.width;
  5671.         int new_height = (CURR_INPUTBOX.height == INPUTBOX_DEFAULT) ? rect.bottom - rect.top : CURR_INPUTBOX.height;
  5672.  
  5673.         // If a non-default size was specified, the box will need to be recentered; thus, we can't rely on
  5674.         // the dialog's DS_CENTER style in its template.  The exception is when an explicit xpos or ypos is
  5675.         // specified, in which case centering is disabled for that dimension.
  5676.         int new_xpos, new_ypos;
  5677.         if (CURR_INPUTBOX.xpos != INPUTBOX_DEFAULT && CURR_INPUTBOX.ypos != INPUTBOX_DEFAULT)
  5678.         {
  5679.             new_xpos = CURR_INPUTBOX.xpos;
  5680.             new_ypos = CURR_INPUTBOX.ypos;
  5681.         }
  5682.         else
  5683.         {
  5684.             POINT pt = CenterWindow(new_width, new_height);
  5685.               if (CURR_INPUTBOX.xpos == INPUTBOX_DEFAULT) // Center horizontally.
  5686.                 new_xpos = pt.x;
  5687.             else
  5688.                 new_xpos = CURR_INPUTBOX.xpos;
  5689.               if (CURR_INPUTBOX.ypos == INPUTBOX_DEFAULT) // Center vertically.
  5690.                 new_ypos = pt.y;
  5691.             else
  5692.                 new_ypos = CURR_INPUTBOX.ypos;
  5693.         }
  5694.  
  5695.         MoveWindow(hWndDlg, new_xpos, new_ypos, new_width, new_height, TRUE);  // Do repaint.
  5696.         // This may also needed to make it redraw in some OSes or some conditions:
  5697.         GetClientRect(hWndDlg, &rect);  // Not to be confused with GetWindowRect().
  5698.         SendMessage(hWndDlg, WM_SIZE, SIZE_RESTORED, rect.right + (rect.bottom<<16));
  5699.         
  5700.         if (*CURR_INPUTBOX.default_string)
  5701.             SetDlgItemText(hWndDlg, IDC_INPUTEDIT, CURR_INPUTBOX.default_string);
  5702.  
  5703.         if (hWndDlg != GetForegroundWindow()) // Normally it will be foreground since the template has this property.
  5704.             SetForegroundWindowEx(hWndDlg);   // Try to force it to the foreground.
  5705.  
  5706.         // Setting the small icon puts it in the upper left corner of the dialog window.
  5707.         // Setting the big icon makes the dialog show up correctly in the Alt-Tab menu.
  5708.         LPARAM main_icon = (LPARAM)(g_script.mCustomIcon ? g_script.mCustomIcon
  5709.             : LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_MAIN), IMAGE_ICON, 0, 0, LR_SHARED)); // Use LR_SHARED to conserve memory (since the main icon is loaded for so many purposes).
  5710.         SendMessage(hWndDlg, WM_SETICON, ICON_SMALL, main_icon);
  5711.         SendMessage(hWndDlg, WM_SETICON, ICON_BIG, main_icon);
  5712.  
  5713.         // For the timeout, use a timer ID that doesn't conflict with MsgBox's IDs (which are the
  5714.         // integers 1 through the max allowed number of msgboxes).  Use +3 vs. +1 for a margin of safety
  5715.         // (e.g. in case a few extra MsgBoxes can be created directly by the program and not by
  5716.         // the script):
  5717.         #define INPUTBOX_TIMER_ID_OFFSET (MAX_MSGBOXES + 3)
  5718.         if (CURR_INPUTBOX.timeout)
  5719.             SetTimer(hWndDlg, INPUTBOX_TIMER_ID_OFFSET + target_index, CURR_INPUTBOX.timeout, InputBoxTimeout);
  5720.  
  5721.         return TRUE; // i.e. let the system set the keyboard focus to the first visible control.
  5722.     }
  5723.  
  5724.     case WM_SIZE:
  5725.     {
  5726.         // Adapted from D.Nuttall's InputBox in the AutotIt3 source.
  5727.  
  5728.         // don't try moving controls if minimized
  5729.         if (wParam == SIZE_MINIMIZED)
  5730.             return TRUE;
  5731.  
  5732.         int dlg_new_width = LOWORD(lParam);
  5733.         int dlg_new_height = HIWORD(lParam);
  5734.  
  5735.         int last_ypos = 0, curr_width, curr_height;
  5736.  
  5737.         // Changing these might cause weird effects when user resizes the window since the default size and
  5738.         // margins is about 5 (as stored in the dialog's resource properties).  UPDATE: That's no longer
  5739.         // an issue since the dialog is resized when the dialog is first displayed to make sure everything
  5740.         // behaves consistently:
  5741.         const int XMargin = 5, YMargin = 5;
  5742.  
  5743.         RECT rTmp;
  5744.  
  5745.         // start at the bottom - OK button
  5746.  
  5747.         HWND hbtOk = GetDlgItem(hWndDlg, IDOK);
  5748.         if (hbtOk != NULL)
  5749.         {
  5750.             // how big is the control?
  5751.             GetWindowRect(hbtOk, &rTmp);
  5752.             if (rTmp.left > rTmp.right)
  5753.                 swap(rTmp.left, rTmp.right);
  5754.             if (rTmp.top > rTmp.bottom)
  5755.                 swap(rTmp.top, rTmp.bottom);
  5756.             curr_width = rTmp.right - rTmp.left;
  5757.             curr_height = rTmp.bottom - rTmp.top;
  5758.             last_ypos = dlg_new_height - YMargin - curr_height;
  5759.             // where to put the control?
  5760.             MoveWindow(hbtOk, dlg_new_width/4+(XMargin-curr_width)/2, last_ypos, curr_width, curr_height, FALSE);
  5761.         }
  5762.  
  5763.         // Cancel Button
  5764.         HWND hbtCancel = GetDlgItem(hWndDlg, IDCANCEL);
  5765.         if (hbtCancel != NULL)
  5766.         {
  5767.             // how big is the control?
  5768.             GetWindowRect(hbtCancel, &rTmp);
  5769.             if (rTmp.left > rTmp.right)
  5770.                 swap(rTmp.left, rTmp.right);
  5771.             if (rTmp.top > rTmp.bottom)
  5772.                 swap(rTmp.top, rTmp.bottom);
  5773.             curr_width = rTmp.right - rTmp.left;
  5774.             curr_height = rTmp.bottom - rTmp.top;
  5775.             // where to put the control?
  5776.             MoveWindow(hbtCancel, dlg_new_width*3/4-(XMargin+curr_width)/2, last_ypos, curr_width, curr_height, FALSE);
  5777.         }
  5778.  
  5779.         // Edit Box
  5780.         HWND hedText = GetDlgItem(hWndDlg, IDC_INPUTEDIT);
  5781.         if (hedText != NULL)
  5782.         {
  5783.             // how big is the control?
  5784.             GetWindowRect(hedText, &rTmp);
  5785.             if (rTmp.left > rTmp.right)
  5786.                 swap(rTmp.left, rTmp.right);
  5787.             if (rTmp.top > rTmp.bottom)
  5788.                 swap(rTmp.top, rTmp.bottom);
  5789.             curr_width = rTmp.right - rTmp.left;
  5790.             curr_height = rTmp.bottom - rTmp.top;
  5791.             last_ypos -= 5 + curr_height;  // Allows space between the buttons and the edit box.
  5792.             // where to put the control?
  5793.             MoveWindow(hedText, XMargin, last_ypos, dlg_new_width - XMargin*2
  5794.                 , curr_height, FALSE);
  5795.         }
  5796.  
  5797.         // Static Box (Prompt)
  5798.         HWND hstPrompt = GetDlgItem(hWndDlg, IDC_INPUTPROMPT);
  5799.         if (hstPrompt != NULL)
  5800.         {
  5801.             last_ypos -= 10;  // Allows space between the edit box and the prompt (static text area).
  5802.             // where to put the control?
  5803.             MoveWindow(hstPrompt, XMargin, YMargin, dlg_new_width - XMargin*2
  5804.                 , last_ypos, FALSE);
  5805.         }
  5806.         InvalidateRect(hWndDlg, NULL, TRUE);    // force window to be redrawn
  5807.         return TRUE;  // i.e. completely handled here.
  5808.     }
  5809.  
  5810.     case WM_COMMAND:
  5811.         // In this case, don't use (g_nInputBoxes - 1) as the index because it might
  5812.         // not correspond to the g_InputBox[] array element that belongs to hWndDlg.
  5813.         // This is because more than one input box can be on the screen at the same time.
  5814.         // If the user choses to work with on underneath instead of the most recent one,
  5815.         // we would be called with an hWndDlg whose index is less than the most recent
  5816.         // one's index (g_nInputBoxes - 1).  Instead, search the array for a match.
  5817.         // Work backward because the most recent one(s) are more likely to be a match:
  5818.         for (; target_index > -1; --target_index)
  5819.             if (g_InputBox[target_index].hwnd == hWndDlg)
  5820.                 break;
  5821.         if (target_index < 0)  // Should never happen if things are designed right.
  5822.             return FALSE;
  5823.         switch (LOWORD(wParam))
  5824.         {
  5825.         case IDOK:
  5826.         case IDCANCEL:
  5827.         {
  5828.             WORD return_value = LOWORD(wParam);  // Set default, i.e. IDOK or IDCANCEL
  5829.             if (   !(hControl = GetDlgItem(hWndDlg, IDC_INPUTEDIT))   )
  5830.                 return_value = (WORD)FAIL;
  5831.             else
  5832.             {
  5833.                 // For AutoIt2 (.aut) script:
  5834.                 // If the user presses the cancel button, we set the output variable to be blank so
  5835.                 // that there is a way to detect that the cancel button was pressed.  This is because
  5836.                 // the InputBox command does not set ErrorLevel for .aut scripts (to maximize backward
  5837.                 // compatibility), except in the case of a timeout.
  5838.                 // For non-AutoIt2 scripts: The output variable is set to whatever the user entered,
  5839.                 // even if the user pressed the cancel button.  This allows the cancel button to specify
  5840.                 // that a different operation should be performed on the entered text.
  5841.                 // NOTE: ErrorLevel must not be set here because it's possible that the user has
  5842.                 // dismissed a dialog that's underneath another, active dialog, or that's currently
  5843.                 // suspended due to a timed/hotkey subroutine running on top of it.  In other words,
  5844.                 // it's only safe to set ErrorLevel when the call to DialogProc() returns in InputBox().
  5845.                 #define SET_OUTPUT_VAR_TO_BLANK (LOWORD(wParam) == IDCANCEL && g_script.mIsAutoIt2)
  5846.                 #undef INPUTBOX_VAR
  5847.                 #define INPUTBOX_VAR (CURR_INPUTBOX.output_var)
  5848.                 VarSizeType space_needed = SET_OUTPUT_VAR_TO_BLANK ? 1 : GetWindowTextLength(hControl) + 1;
  5849.                 // Set up the var, enlarging it if necessary.  If the output_var is of type VAR_CLIPBOARD,
  5850.                 // this call will set up the clipboard for writing:
  5851.                 if (INPUTBOX_VAR->Assign(NULL, space_needed - 1) != OK)
  5852.                     // It will have already displayed the error.  Displaying errors in a callback
  5853.                     // function like this one isn't that good, since the callback won't return
  5854.                     // to its caller in a timely fashion.  However, these type of errors are so
  5855.                     // rare it's not a priority to change all the called functions (and the functions
  5856.                     // they call) to skip the displaying of errors and just return FAIL instead.
  5857.                     // In addition, this callback function has been tested with a MsgBox() call
  5858.                     // inside and it doesn't seem to cause any crashes or undesirable behavior other
  5859.                     // than the fact that the InputBox window is not dismissed until the MsgBox
  5860.                     // window is dismissed:
  5861.                     return_value = (WORD)FAIL;
  5862.                 else
  5863.                 {
  5864.                     // Write to the variable:
  5865.                     if (SET_OUTPUT_VAR_TO_BLANK)
  5866.                         // It's length was already set by the above call to Assign().
  5867.                         *INPUTBOX_VAR->Contents() = '\0';
  5868.                     else
  5869.                     {
  5870.                         INPUTBOX_VAR->Length() = (VarSizeType)GetWindowText(hControl
  5871.                             , INPUTBOX_VAR->Contents(), space_needed);
  5872.                         if (!INPUTBOX_VAR->Length())
  5873.                             // There was no text to get or GetWindowText() failed.
  5874.                             *INPUTBOX_VAR->Contents() = '\0';  // Safe because Assign() gave us a non-constant memory area.
  5875.                     }
  5876.                     if (INPUTBOX_VAR->Close() != OK)  // In case it's the clipboard.
  5877.                         return_value = (WORD)FAIL;
  5878.                 }
  5879.             }
  5880.             // Since the user pressed a button to dismiss the dialog:
  5881.             // Kill its timer for performance reasons (might degrade perf. a little since OS has
  5882.             // to keep track of it as long as it exists).  InputBoxTimeout() already handles things
  5883.             // right even if we don't do this:
  5884.             if (CURR_INPUTBOX.timeout) // It has a timer.
  5885.                 KillTimer(hWndDlg, INPUTBOX_TIMER_ID_OFFSET + target_index);
  5886.             EndDialog(hWndDlg, return_value);
  5887.             return TRUE;
  5888.         } // case
  5889.         } // Inner switch()
  5890.     } // Outer switch()
  5891.     // Otherwise, let the dialog handler do its default action:
  5892.     return FALSE;
  5893. }
  5894.  
  5895.  
  5896.  
  5897. VOID CALLBACK InputBoxTimeout(HWND hWnd, UINT uMsg, UINT idEvent, DWORD dwTime)
  5898. {
  5899.     // First check if the window has already been destroyed.  There are quite a few ways this can
  5900.     // happen, and in all of them we want to make sure not to do things such as calling EndDialog()
  5901.     // again or updating the output variable.  Reasons:
  5902.     // 1) The user has already pressed the OK or Cancel button (the timer isn't killed there because
  5903.     //    it relies on us doing this check here).  In this case, EndDialog() has already been called
  5904.     //    (with the proper result value) and the script's output variable has already been set.
  5905.     // 2) Even if we were to kill the timer when the user presses a button to dismiss the dialog,
  5906.     //    this IsWindow() check would still be needed here because TimerProc()'s are called via
  5907.     //    WM_TIMER messages, some of which might still be in our msg queue even after the timer
  5908.     //    has been killed.  In other words, split second timing issues may cause this TimerProc()
  5909.     //    to fire even if the timer were killed when the user dismissed the dialog.
  5910.     // UPDATE: For performance reasons, the timer is now killed when the user presses a button,
  5911.     // so case #1 is obsolete (but kept here for background/insight).
  5912.     if (IsWindow(hWnd))
  5913.     {
  5914.         // This is the element in the array that corresponds to the InputBox for which
  5915.         // this function has just been called.
  5916.         int target_index = idEvent - INPUTBOX_TIMER_ID_OFFSET;
  5917.         // Even though the dialog has timed out, we still want to write anything the user
  5918.         // had a chance to enter into the output var.  This is because it's conceivable that
  5919.         // someone might want a short timeout just to enter something quick and let the
  5920.         // timeout dismiss the dialog for them (i.e. so that they don't have to press enter
  5921.         // or a button:
  5922.         HWND hControl = GetDlgItem(hWnd, IDC_INPUTEDIT);
  5923.         if (hControl)
  5924.         {
  5925.             #undef INPUTBOX_VAR
  5926.             #define INPUTBOX_VAR (g_InputBox[target_index].output_var)
  5927.             VarSizeType space_needed = GetWindowTextLength(hControl) + 1;
  5928.             // Set up the var, enlarging it if necessary.  If the output_var is of type VAR_CLIPBOARD,
  5929.             // this call will set up the clipboard for writing:
  5930.             if (INPUTBOX_VAR->Assign(NULL, space_needed - 1) == OK)
  5931.             {
  5932.                 // Write to the variable:
  5933.                 INPUTBOX_VAR->Length() = (VarSizeType)GetWindowText(hControl
  5934.                     , INPUTBOX_VAR->Contents(), space_needed);
  5935.                 if (!INPUTBOX_VAR->Length())
  5936.                     // There was no text to get or GetWindowText() failed.
  5937.                     *INPUTBOX_VAR->Contents() = '\0';  // Safe because Assign() gave us a non-constant memory area.
  5938.                 INPUTBOX_VAR->Close();  // In case it's the clipboard.
  5939.             }
  5940.         }
  5941.         EndDialog(hWnd, AHK_TIMEOUT);
  5942.     }
  5943.     KillTimer(hWnd, idEvent);
  5944. }
  5945.  
  5946.  
  5947.  
  5948. VOID CALLBACK DerefTimeout(HWND hWnd, UINT uMsg, UINT idEvent, DWORD dwTime)
  5949. {
  5950.     Line::FreeDerefBufIfLarge(); // It will also kill the timer, if appropriate.
  5951. }
  5952.  
  5953.  
  5954.  
  5955. ResultType Line::MouseGetPos(DWORD aOptions)
  5956. // Returns OK or FAIL.
  5957. {
  5958.     // Caller should already have ensured that at least one of these will be non-NULL.
  5959.     // The only time this isn't true is for dynamically-built variable names.  In that
  5960.     // case, we don't worry about it if it's NULL, since the user will already have been
  5961.     // warned:
  5962.     Var *output_var_x = ARGVAR1;  // Ok if NULL.
  5963.     Var *output_var_y = ARGVAR2;  // Ok if NULL.
  5964.     Var *output_var_parent = ARGVAR3;  // Ok if NULL.
  5965.     Var *output_var_child = ARGVAR4;  // Ok if NULL.
  5966.  
  5967.     POINT point;
  5968.     GetCursorPos(&point);  // Realistically, can't fail?
  5969.  
  5970.     RECT rect = {0};  // ensure it's initialized for later calculations.
  5971.     if (!(g.CoordMode & COORD_MODE_MOUSE)) // Using relative vs. absolute coordinates.
  5972.     {
  5973.         HWND fore_win = GetForegroundWindow();
  5974.         GetWindowRect(fore_win, &rect);  // If this call fails, above default values will be used.
  5975.     }
  5976.  
  5977.     if (output_var_x) // else the user didn't want the X coordinate, just the Y.
  5978.         if (!output_var_x->Assign(point.x - rect.left))
  5979.             return FAIL;
  5980.     if (output_var_y) // else the user didn't want the Y coordinate, just the X.
  5981.         if (!output_var_y->Assign(point.y - rect.top))
  5982.             return FAIL;
  5983.  
  5984.     if (!output_var_parent && !output_var_child)
  5985.         return OK;
  5986.  
  5987.     // This is the child window.  Despite what MSDN says, WindowFromPoint() appears to fetch
  5988.     // a non-NULL value even when the mouse is hovering over a disabled control (at least on XP).
  5989.     HWND child_under_cursor = WindowFromPoint(point);
  5990.     if (!child_under_cursor)
  5991.     {
  5992.         if (output_var_parent)
  5993.             output_var_parent->Assign();
  5994.         if (output_var_child)
  5995.             output_var_child->Assign();
  5996.         return OK;
  5997.     }
  5998.  
  5999.     HWND parent_under_cursor = GetNonChildParent(child_under_cursor);  // Find the first ancestor that isn't a child.
  6000.     if (output_var_parent)
  6001.     {
  6002.         // Testing reveals that an invisible parent window never obscures another window beneath it as seen by
  6003.         // WindowFromPoint().  In other words, the below never happens, so there's no point in having it as a
  6004.         // documented feature:
  6005.         //if (!g.DetectHiddenWindows && !IsWindowVisible(parent_under_cursor))
  6006.         //    return output_var_parent->Assign();
  6007.         if (!output_var_parent->AssignHWND(parent_under_cursor))
  6008.             return FAIL;
  6009.     }
  6010.  
  6011.     if (!output_var_child)
  6012.         return OK;
  6013.  
  6014.     // Doing it this way overcomes the limitations of WindowFromPoint() and ChildWindowFromPoint()
  6015.     // and also better matches the control that Window Spy would think is under the cursor:
  6016.     if (!(aOptions & 0x01)) // Not in simple mode, so find the control the normal/complex way.
  6017.     {
  6018.         point_and_hwnd_type pah = {0};
  6019.         pah.pt = point;
  6020.         EnumChildWindows(parent_under_cursor, EnumChildFindPoint, (LPARAM)&pah); // Find topmost control containing point.
  6021.         if (pah.hwnd_found)
  6022.             child_under_cursor = pah.hwnd_found;
  6023.     }
  6024.     //else as of v1.0.25.10, leave child_under_cursor set the the value retrieved earlier from WindowFromPoint().
  6025.     // This allows MDI child windows to be reported correctly; i.e. that the window on top of the others
  6026.     // is reported rather than the one at the top of the z-order (the z-order of MDI child windows,
  6027.     // although probably constant, is not useful for determine which one is one top of the others).
  6028.  
  6029.     if (parent_under_cursor == child_under_cursor) // if there's no control per se, make it blank.
  6030.         return output_var_child->Assign();
  6031.  
  6032.     if (aOptions & 0x02) // v1.0.43.06: Bitwise flag that means "return control's HWND vs. ClassNN".
  6033.         return output_var_child->AssignHWND(child_under_cursor);
  6034.  
  6035.     class_and_hwnd_type cah;
  6036.     cah.hwnd = child_under_cursor;  // This is the specific control we need to find the sequence number of.
  6037.     char class_name[WINDOW_CLASS_SIZE];
  6038.     cah.class_name = class_name;
  6039.     if (!GetClassName(cah.hwnd, class_name, sizeof(class_name) - 5))  // -5 to allow room for sequence number.
  6040.         return output_var_child->Assign();
  6041.     cah.class_count = 0;  // Init for the below.
  6042.     cah.is_found = false; // Same.
  6043.     EnumChildWindows(parent_under_cursor, EnumChildFindSeqNum, (LPARAM)&cah); // Find this control's seq. number.
  6044.     if (!cah.is_found)
  6045.         return output_var_child->Assign();  
  6046.     // Append the class sequence number onto the class name and set the output param to be that value:
  6047.     snprintfcat(class_name, sizeof(class_name), "%d", cah.class_count);
  6048.     return output_var_child->Assign(class_name);
  6049. }
  6050.  
  6051.  
  6052.  
  6053. BOOL CALLBACK EnumChildFindPoint(HWND aWnd, LPARAM lParam)
  6054. // This is called by more than one caller.  It finds the most appropriate child window that contains
  6055. // the specified point (the point should be in screen coordinates).
  6056. {
  6057.     point_and_hwnd_type &pah = *(point_and_hwnd_type *)lParam;  // For performance and convenience.
  6058.     if (!IsWindowVisible(aWnd)) // Omit hidden controls, like Window Spy does.
  6059.         return TRUE;
  6060.     RECT rect;
  6061.     if (!GetWindowRect(aWnd, &rect))
  6062.         return TRUE;
  6063.     // The given point must be inside aWnd's bounds.  Then, if there is no hwnd found yet or if aWnd
  6064.     // is entirely contained within the previously found hwnd, update to a "better" found window like
  6065.     // Window Spy.  This overcomes the limitations of WindowFromPoint() and ChildWindowFromPoint():
  6066.     if (pah.pt.x >= rect.left && pah.pt.x <= rect.right && pah.pt.y >= rect.top && pah.pt.y <= rect.bottom)
  6067.     {
  6068.         // If the window's center is closer to the given point, break the tie and have it take
  6069.         // precedence.  This solves the problem where a particular control from a set of overlapping
  6070.         // controls is chosen arbitrarily (based on Z-order) rather than based on something the
  6071.         // user would find more intuitive (the control whose center is closest to the mouse):
  6072.         double center_x = rect.left + (double)(rect.right - rect.left) / 2;
  6073.         double center_y = rect.top + (double)(rect.bottom - rect.top) / 2;
  6074.         // Taking the absolute value first is not necessary because it seems that qmathHypot()
  6075.         // takes the square root of the sum of the squares, which handles negatives correctly:
  6076.         double distance = qmathHypot(pah.pt.x - center_x, pah.pt.y - center_y);
  6077.         //double distance = qmathSqrt(qmathPow(pah.pt.x - center_x, 2) + qmathPow(pah.pt.y - center_y, 2));
  6078.         bool update_it = !pah.hwnd_found;
  6079.         if (!update_it)
  6080.         {
  6081.             // If the new window's rect is entirely contained within the old found-window's rect, update
  6082.             // even if the distance is greater.  Conversely, if the new window's rect entirely encloses
  6083.             // the old window's rect, do not update even if the distance is less:
  6084.             if (rect.left >= pah.rect_found.left && rect.right <= pah.rect_found.right
  6085.                 && rect.top >= pah.rect_found.top && rect.bottom <= pah.rect_found.bottom)
  6086.                 update_it = true; // New is entirely enclosed by old: update to the New.
  6087.             else if (   distance < pah.distance &&
  6088.                 (pah.rect_found.left < rect.left || pah.rect_found.right > rect.right
  6089.                     || pah.rect_found.top < rect.top || pah.rect_found.bottom > rect.bottom)   )
  6090.                 update_it = true; // New doesn't entirely enclose old and new's center is closer to the point.
  6091.         }
  6092.         if (update_it)
  6093.         {
  6094.             pah.hwnd_found = aWnd;
  6095.             pah.rect_found = rect; // And at least one caller uses this returned rect.
  6096.             pah.distance = distance;
  6097.         }
  6098.     }
  6099.     return TRUE; // Continue enumeration all the way through.
  6100. }
  6101.  
  6102.  
  6103.  
  6104. ///////////////////////////////
  6105. // Related to other commands //
  6106. ///////////////////////////////
  6107.  
  6108. ResultType Line::FormatTime(char *aYYYYMMDD, char *aFormat)
  6109. // The compressed code size of this function is about 1 KB (2 KB uncompressed), which compares
  6110. // favorably to using setlocale()+strftime(), which together are about 8 KB of compressed code
  6111. // (setlocale() seems to be needed to put the user's or system's locale into effect for strftime()).
  6112. // setlocale() weighs in at about 6.5 KB compressed (14 KB uncompressed).
  6113. {
  6114.     Var &output_var = *OUTPUT_VAR;
  6115.  
  6116.     #define FT_MAX_INPUT_CHARS 2000  // In preparation for future use of TCHARs, since GetDateFormat() uses char-count not size.
  6117.     // Input/format length is restricted since it must be translated and expanded into a new format
  6118.     // string that uses single quotes around non-alphanumeric characters such as punctuation:
  6119.     if (strlen(aFormat) > FT_MAX_INPUT_CHARS)
  6120.         return output_var.Assign();
  6121.  
  6122.     // Worst case expansion: .d.d.d.d. (9 chars) --> '.'d'.'d'.'d'.'d'.' (19 chars)
  6123.     // Buffer below is sized to a little more than twice as big as the largest allowed format,
  6124.     // which avoids having to constantly check for buffer overflow while translating aFormat
  6125.     // into format_buf:
  6126.     #define FT_MAX_OUTPUT_CHARS (2*FT_MAX_INPUT_CHARS + 10)
  6127.     char format_buf[FT_MAX_OUTPUT_CHARS + 1];
  6128.     char output_buf[FT_MAX_OUTPUT_CHARS + 1]; // The size of this is somewhat arbitrary, but buffer overflow is checked so it's safe.
  6129.  
  6130.     char yyyymmdd[256] = ""; // Large enough to hold date/time and any options that follow it (note that D and T options can appear multiple times).
  6131.  
  6132.     SYSTEMTIME st;
  6133.     char *options = NULL;
  6134.  
  6135.     if (!*aYYYYMMDD) // Use current local time by default.
  6136.         GetLocalTime(&st);
  6137.     else
  6138.     {
  6139.         strlcpy(yyyymmdd, omit_leading_whitespace(aYYYYMMDD), sizeof(yyyymmdd)); // Make a modifiable copy.
  6140.         if (*yyyymmdd < '0' || *yyyymmdd > '9') // First character isn't a digit, therefore...
  6141.         {
  6142.             // ... options are present without date (since yyyymmdd [if present] must come before options).
  6143.             options = yyyymmdd;
  6144.             GetLocalTime(&st);  // Use current local time by default.
  6145.         }
  6146.         else // Since the string starts with a digit, rules say it must be a YYYYMMDD string, possibly followed by options.
  6147.         {
  6148.             // Find first space or tab because YYYYMMDD portion might contain only the leading part of date/timestamp.
  6149.             if (options = StrChrAny(yyyymmdd, " \t")) // Find space or tab.
  6150.             {
  6151.                 *options = '\0'; // Terminate yyyymmdd at the end of the YYYYMMDDHH24MISS string.
  6152.                 options = omit_leading_whitespace(++options); // Point options to the right place (can be empty string).
  6153.             }
  6154.             //else leave options set to NULL to indicate that there are none.
  6155.  
  6156.             // Pass "false" for validatation so that times can still be reported even if the year
  6157.             // is prior to 1601.  If the time and/or date is invalid, GetTimeFormat() and GetDateFormat()
  6158.             // will refuse to produce anything, which is documented behavior:
  6159.             YYYYMMDDToSystemTime(yyyymmdd, st, false);
  6160.         }
  6161.     }
  6162.     
  6163.     // Set defaults.  Some can be overridden by options (if there are any options).
  6164.     LCID lcid = LOCALE_USER_DEFAULT;
  6165.     DWORD date_flags = 0, time_flags = 0;
  6166.     bool date_flags_specified = false, time_flags_specified = false, reverse_date_time = false;
  6167.     #define FT_FORMAT_NONE 0
  6168.     #define FT_FORMAT_TIME 1
  6169.     #define FT_FORMAT_DATE 2
  6170.     int format_type1 = FT_FORMAT_NONE;
  6171.     char *format2_marker = NULL; // Will hold the location of the first char of the second format (if present).
  6172.     bool do_null_format2 = false;  // Will be changed to true if a default date *and* time should be used.
  6173.  
  6174.     if (options) // Parse options.
  6175.     {
  6176.         char *option_end, orig_char;
  6177.         for (char *next_option = options; *next_option; next_option = omit_leading_whitespace(option_end))
  6178.         {
  6179.             // Find the end of this option item:
  6180.             if (   !(option_end = StrChrAny(next_option, " \t"))   )  // Space or tab.
  6181.                 option_end = next_option + strlen(next_option); // Set to position of zero terminator instead.
  6182.  
  6183.             // Permanently terminate in between options to help eliminate ambiguity for words contained
  6184.             // inside other words, and increase confidence in decimal and hexadecimal conversion.
  6185.             orig_char = *option_end;
  6186.             *option_end = '\0';
  6187.  
  6188.             ++next_option;
  6189.             switch (toupper(next_option[-1]))
  6190.             {
  6191.             case 'D':
  6192.                 date_flags_specified = true;
  6193.                 date_flags |= ATOU(next_option); // ATOU() for unsigned.
  6194.                 break;
  6195.             case 'T':
  6196.                 time_flags_specified = true;
  6197.                 time_flags |= ATOU(next_option); // ATOU() for unsigned.
  6198.                 break;
  6199.             case 'R':
  6200.                 reverse_date_time = true;
  6201.                 break;
  6202.             case 'L':
  6203.                 lcid = !stricmp(next_option, "Sys") ? LOCALE_SYSTEM_DEFAULT : (LCID)ATOU(next_option);
  6204.                 break;
  6205.             // If not one of the above, such as zero terminator or a number, just ignore it.
  6206.             }
  6207.  
  6208.             *option_end = orig_char; // Undo the temporary termination so that loop's omit_leading() will work.
  6209.         } // for() each item in option list
  6210.     } // Parse options.
  6211.  
  6212.     if (!*aFormat)
  6213.     {
  6214.         aFormat = NULL; // Tell GetDateFormat() and GetTimeFormat() to use default for the specified locale.
  6215.         if (!date_flags_specified) // No preference was given, so use long (which seems generally more useful).
  6216.             date_flags |= DATE_LONGDATE;
  6217.         if (!time_flags_specified)
  6218.             time_flags |= TIME_NOSECONDS;  // Seems more desirable/typical to default to no seconds.
  6219.         // Put the time first by default, though this is debatable (Metapad does it and I like it).
  6220.         format_type1 = reverse_date_time ? FT_FORMAT_DATE : FT_FORMAT_TIME;
  6221.         do_null_format2 = true;
  6222.     }
  6223.     else // aFormat is non-blank.
  6224.     {
  6225.         // Omit whitespace only for consideration of special keywords.  Whitespace is later kept for
  6226.         // a normal format string such as %A_Space%MM/dd/yy:
  6227.         char *candidate = omit_leading_whitespace(aFormat);
  6228.         if (!stricmp(candidate, "YWeek"))
  6229.         {
  6230.             GetISOWeekNumber(output_buf, st.wYear, GetYDay(st.wMonth, st.wDay, IS_LEAP_YEAR(st.wYear)), st.wDayOfWeek);
  6231.             return output_var.Assign(output_buf);
  6232.         }
  6233.         if (!stricmp(candidate, "YDay") || !stricmp(candidate, "YDay0"))
  6234.         {
  6235.             int yday = GetYDay(st.wMonth, st.wDay, IS_LEAP_YEAR(st.wYear));
  6236.             if (!stricmp(candidate, "YDay"))
  6237.                 return output_var.Assign(yday); // Assign with no leading zeroes, also will be in hex format if that format is in effect.
  6238.             // Otherwise:
  6239.             sprintf(output_buf, "%03d", yday);
  6240.             return output_var.Assign(output_buf);
  6241.         }
  6242.         if (!stricmp(candidate, "WDay"))
  6243.             return output_var.Assign(st.wDayOfWeek + 1);  // Convert to 1-based for compatibility with A_WDay.
  6244.  
  6245.         // Since above didn't return, check for those that require a call to GetTimeFormat/GetDateFormat
  6246.         // further below:
  6247.         if (!stricmp(candidate, "ShortDate"))
  6248.         {
  6249.             aFormat = NULL;
  6250.             date_flags |= DATE_SHORTDATE;
  6251.             date_flags &= ~(DATE_LONGDATE | DATE_YEARMONTH); // If present, these would prevent it from working.
  6252.         }
  6253.         else if (!stricmp(candidate, "LongDate"))
  6254.         {
  6255.             aFormat = NULL;
  6256.             date_flags |= DATE_LONGDATE;
  6257.             date_flags &= ~(DATE_SHORTDATE | DATE_YEARMONTH); // If present, these would prevent it from working.
  6258.         }
  6259.         else if (!stricmp(candidate, "YearMonth"))
  6260.         {
  6261.             aFormat = NULL;
  6262.             date_flags |= DATE_YEARMONTH;
  6263.             date_flags &= ~(DATE_SHORTDATE | DATE_LONGDATE); // If present, these would prevent it from working.
  6264.         }
  6265.         else if (!stricmp(candidate, "Time"))
  6266.         {
  6267.             format_type1 = FT_FORMAT_TIME;
  6268.             aFormat = NULL;
  6269.             if (!time_flags_specified)
  6270.                 time_flags |= TIME_NOSECONDS;  // Seems more desirable/typical to default to no seconds.
  6271.         }
  6272.         else // Assume normal format string.
  6273.         {
  6274.             char *cp = aFormat, *dp = format_buf;   // Initialize source and destination pointers.
  6275.             bool inside_their_quotes = false; // Whether we are inside a single-quoted string in the source.
  6276.             bool inside_our_quotes = false;   // Whether we are inside a single-quoted string of our own making in dest.
  6277.             for (; *cp; ++cp) // Transcribe aFormat into format_buf and also check for which comes first: date or time.
  6278.             {
  6279.                 if (*cp == '\'') // Note that '''' (four consecutive quotes) is a single literal quote, which this logic handles okay.
  6280.                 {
  6281.                     if (inside_our_quotes)
  6282.                     {
  6283.                         // Upon encountering their quotes while we're still in ours, merge theirs with ours and
  6284.                         // remark it as theirs.  This is done to avoid having two back-to-back quoted sections,
  6285.                         // which would result in an unwanted literal single quote.  Example:
  6286.                         // 'Some string'':' (the two quotes in the middle would be seen as a literal quote).
  6287.                         inside_our_quotes = false;
  6288.                         inside_their_quotes = true;
  6289.                         continue;
  6290.                     }
  6291.                     if (inside_their_quotes)
  6292.                     {
  6293.                         // If next char needs to be quoted, don't close out this quote section because that
  6294.                         // would introduce two consecutive quotes, which would be interpreted as a single
  6295.                         // literal quote if its enclosed by two outer single quotes.  Instead convert this
  6296.                         // quoted section over to "ours":
  6297.                         if (cp[1] && !IsCharAlphaNumeric(cp[1]) && cp[1] != '\'') // Also consider single quotes to be theirs due to this example: dddd:''''y
  6298.                             inside_our_quotes = true;
  6299.                             // And don't do "*dp++ = *cp"
  6300.                         else // there's no next-char or it's alpha-numeric, so it doesn't need to be inside quotes.
  6301.                             *dp++ = *cp; // Close out their quoted section.
  6302.                     }
  6303.                     else // They're starting a new quoted section, so just transcribe this single quote as-is.
  6304.                         *dp++ = *cp;
  6305.                     inside_their_quotes = !inside_their_quotes; // Must be done after the above.
  6306.                     continue;
  6307.                 }
  6308.                 // Otherwise, it's not a single quote.
  6309.                 if (inside_their_quotes) // *cp is inside a single-quoted string, so it can be part of format/picture
  6310.                     *dp++ = *cp; // Transcribe as-is.
  6311.                 else
  6312.                 {
  6313.                     if (IsCharAlphaNumeric(*cp))
  6314.                     {
  6315.                         if (inside_our_quotes)
  6316.                         {
  6317.                             *dp++ = '\''; // Close out the previous quoted section, since this char should not be a part of it.
  6318.                             inside_our_quotes = false;
  6319.                         }
  6320.                         if (strchr("dMyg", *cp)) // A format unique to Date is present.
  6321.                         {
  6322.                             if (!format_type1)
  6323.                                 format_type1 = FT_FORMAT_DATE;
  6324.                             else if (format_type1 == FT_FORMAT_TIME && !format2_marker) // type2 should only be set if different than type1.
  6325.                             {
  6326.                                 *dp++ = '\0';  // Terminate the first section and (below) indicate that there's a second.
  6327.                                 format2_marker = dp;  // Point it to the location in format_buf where the split should occur.
  6328.                             }
  6329.                         }
  6330.                         else if (strchr("hHmst", *cp)) // A format unique to Time is present.
  6331.                         {
  6332.                             if (!format_type1)
  6333.                                 format_type1 = FT_FORMAT_TIME;
  6334.                             else if (format_type1 == FT_FORMAT_DATE && !format2_marker) // type2 should only be set if different than type1.
  6335.                             {
  6336.                                 *dp++ = '\0';  // Terminate the first section and (below) indicate that there's a second.
  6337.                                 format2_marker = dp;  // Point it to the location in format_buf where the split should occur.
  6338.                             }
  6339.                         }
  6340.                         // For consistency, transcribe all AlphaNumeric chars not inside single quotes as-is
  6341.                         // (numbers are transcribed in case they are ever used as part of pic/format).
  6342.                         *dp++ = *cp;
  6343.                     }
  6344.                     else // Not alphanumeric, so enclose this and any other non-alphanumeric characters in single quotes.
  6345.                     {
  6346.                         if (!inside_our_quotes)
  6347.                         {
  6348.                             *dp++ = '\''; // Create a new quoted section of our own, since this char should be inside quotes to be understood.
  6349.                             inside_our_quotes = true;
  6350.                         }
  6351.                         *dp++ = *cp;  // Add this character between the quotes, since it's of the right "type".
  6352.                     }
  6353.                 }
  6354.             } // for()
  6355.             if (inside_our_quotes)
  6356.                 *dp++ = '\'';  // Close out our quotes.
  6357.             *dp = '\0'; // Final terminator.
  6358.             aFormat = format_buf; // Point it to the freshly translated format string, for use below.
  6359.         } // aFormat contains normal format/pic string.
  6360.     } // aFormat isn't blank.
  6361.  
  6362.     // If there are no date or time formats present, still do the transcription so that
  6363.     // any quoted strings and whatnot are resolved.  This increases runtime flexibility.
  6364.     // The below is also relied upon by "LongDate" and "ShortDate" above:
  6365.     if (!format_type1)
  6366.         format_type1 = FT_FORMAT_DATE;
  6367.  
  6368.     // MSDN: Time: "The function checks each of the time values to determine that it is within the
  6369.     // appropriate range of values. If any of the time values are outside the correct range, the
  6370.     // function fails, and sets the last-error to ERROR_INVALID_PARAMETER. 
  6371.     // Dates: "...year, month, day, and day of week. If the day of the week is incorrect, the
  6372.     // function uses the correct value, and returns no error. If any of the other date values
  6373.     // are outside the correct range, the function fails, and sets the last-error to ERROR_INVALID_PARAMETER.
  6374.  
  6375.     if (format_type1 == FT_FORMAT_DATE) // DATE comes first.
  6376.     {
  6377.         if (!GetDateFormat(lcid, date_flags, &st, aFormat, output_buf, FT_MAX_OUTPUT_CHARS))
  6378.             *output_buf = '\0';  // Ensure it's still the empty string, then try to continue to get the second half (if there is one).
  6379.     }
  6380.     else // TIME comes first.
  6381.         if (!GetTimeFormat(lcid, time_flags, &st, aFormat, output_buf, FT_MAX_OUTPUT_CHARS))
  6382.             *output_buf = '\0';  // Ensure it's still the empty string, then try to continue to get the second half (if there is one).
  6383.  
  6384.     if (format2_marker || do_null_format2) // There is also a second format present.
  6385.     {
  6386.         size_t output_buf_length = strlen(output_buf);
  6387.         char *output_buf_marker = output_buf + output_buf_length;
  6388.         char *format2;
  6389.         if (do_null_format2)
  6390.         {
  6391.             format2 = NULL;
  6392.             *output_buf_marker++ = ' '; // Provide a space between time and date.
  6393.             ++output_buf_length;
  6394.         }
  6395.         else
  6396.             format2 = format2_marker;
  6397.  
  6398.         int buf_remaining_size = (int)(FT_MAX_OUTPUT_CHARS - output_buf_length);
  6399.         int result;
  6400.  
  6401.         if (format_type1 == FT_FORMAT_DATE) // DATE came first, so this one is TIME.
  6402.             result = GetTimeFormat(lcid, time_flags, &st, format2, output_buf_marker, buf_remaining_size);
  6403.         else
  6404.             result = GetDateFormat(lcid, date_flags, &st, format2, output_buf_marker, buf_remaining_size);
  6405.         if (!result)
  6406.             output_buf[output_buf_length] = '\0'; // Ensure the first part is still terminated and just return that rather than nothing.
  6407.     }
  6408.  
  6409.     return output_var.Assign(output_buf);
  6410. }
  6411.  
  6412.  
  6413.  
  6414. ResultType Line::PerformAssign()
  6415. // Returns OK or FAIL.  Caller has ensured that none of this line's derefs is a function-call.
  6416. {
  6417.     Var *p_output_var; // Can't use OUTPUT_VAR or sArgVar here because ExpandArgs() isn't called prior to PerformAssign().
  6418.     if (   !(p_output_var = ResolveVarOfArg(0))   ) // Fix for v1.0.46.07: Must do this check in case of illegal dynamically-build variable name.
  6419.         return FAIL;
  6420.     p_output_var = p_output_var->ResolveAlias(); // Resolve alias now to detect "source_is_being_appended_to_target" and perhaps other things.
  6421.     Var &output_var = *p_output_var; // For performance.
  6422.     // Now output_var.Type() must be clipboard or normal because otherwise load-time validation (or
  6423.     // ResolveVarOfArg() in GetExpandedArgSize, if it's dynamic) would have prevented us from getting this far.
  6424.  
  6425.     if (mArgc > 1 && ArgHasDeref(2)) // There is at least one deref in Arg #2.
  6426.     {
  6427.         // Can't use sArgVar here because ExecUntil() never calls ExpandArgs() for ACT_ASSIGN.
  6428.         // For simplicity, we don't check that it's the only deref, nor whether it has any literal text
  6429.         // around it, since those things aren't supported anyway.
  6430.         Var *source_var;
  6431.         if (source_var = mArg[1].deref[0].var) // Caller has ensured none of this line's derefs is a function-call, so var should always be the proper member of the union to check.
  6432.         {
  6433.             if (source_var->Type() == VAR_CLIPBOARDALL) // The caller is performing the special mode "Var = %ClipboardAll%".
  6434.                 return output_var.AssignClipboardAll(); // Outsourced to another function to help CPU cache hits/misses in this frequently-called function.
  6435.             if (source_var->IsBinaryClip()) // Caller wants a variable with binary contents assigned (copied) to another variable (usually VAR_CLIPBOARD).
  6436.                 return output_var.AssignBinaryClip(*source_var); // Outsourced to another function to help CPU cache hits/misses in this frequently-called function.
  6437.         }
  6438.     }
  6439.  
  6440.     // Otherwise (since above didn't return):
  6441.     // Find out if output_var (the var being assigned to) is dereferenced (mentioned) in this line's
  6442.     // second arg, which is the value to be assigned.  If it isn't, things are much simpler.
  6443.     // Note: Since Arg#2 for this function is never an output or an input variable, it is not
  6444.     // necessary to check whether its the same variable as Arg#1 for this determination.
  6445.     bool target_is_involved_in_source = false;
  6446.     bool source_is_being_appended_to_target = false; // v1.0.25
  6447.     if (output_var.Type() != VAR_CLIPBOARD && mArgc > 1 // If types is VAR_CLIPBOARD, it can be used in the source deref(s) while also being the target -- without having to use the deref buffer -- because the clipboard has it's own temp buffer: the memory area to which the result is written. The prior content of the clipboard remains available in its other memory area until Commit() is called (i.e. long enough for our purposes).
  6448.         && mArg[1].deref) // ...and there's at least one deref in the arg.
  6449.     {
  6450.         // It has a second arg, which in this case is the value to be assigned to the var.
  6451.         // Examine any derefs that the second arg has to see if output_var is mentioned.
  6452.         // Also, calls to script functions aren't possible within these derefs because
  6453.         // our caller has ensured there are no expressions, and thus no function calls,
  6454.         // inside this line.
  6455.         for (DerefType *deref = mArg[1].deref; deref->marker; ++deref)
  6456.         {
  6457.             if (deref->is_function) // Silent failure, for rare cases such ACT_ASSIGNEXPR calling us due to something like Clipboard:=SavedBinaryClipboard + fn(x) [which isn't valid for binary clipboard]
  6458.                 return FAIL;
  6459.             if (source_is_being_appended_to_target)
  6460.             {
  6461.                 // Check if target is mentioned more than once in source, e.g. Var = %Var%Some Text%Var%
  6462.                 // would be disqualified for the "fast append" method because %Var% occurs more than once.
  6463.                 if (deref->var->ResolveAlias() == p_output_var) // deref->is_function was checked above just in case.
  6464.                 {
  6465.                     source_is_being_appended_to_target = false;
  6466.                     break;
  6467.                 }
  6468.             }
  6469.             else
  6470.             {
  6471.                 if (deref->var->ResolveAlias() == p_output_var) // deref->is_function was checked above just in case.
  6472.                 {
  6473.                     target_is_involved_in_source = true;
  6474.                     // The below disqualifies both of the following cases from the simple-append mode:
  6475.                     // Var = %OtherVar%%Var%   ; Var is not the first item as required.
  6476.                     // Var = LiteralText%Var%  ; Same.
  6477.                     if (deref->marker == mArg[1].text)
  6478.                         source_is_being_appended_to_target = true;
  6479.                         // And continue the loop to ensure that Var is not referenced more than once,
  6480.                         // e.g. Var = %Var%%Var% would be disqualified.
  6481.                     else
  6482.                         break;
  6483.                 }
  6484.             }
  6485.         }
  6486.     }
  6487.  
  6488.     // Note: It might be possible to improve performance in the case where
  6489.     // the target variable is large enough to accommodate the new source data
  6490.     // by moving memory around inside it.  For example, Var1 = xxxxxVar1
  6491.     // could be handled by moving the memory in Var1 to make room to insert
  6492.     // the literal string.  In addition to being quicker than the ExpandArgs()
  6493.     // method, this approach would avoid the possibility of needing to expand the
  6494.     // deref buffer just to handle the operation.  However, if that is ever done,
  6495.     // be sure to check that output_var is mentioned only once in the list of derefs.
  6496.     // For example, something like this would probably be much easier to
  6497.     // implement by using ExpandArgs(): Var1 = xxxx %Var1% %Var2% %Var1% xxxx.
  6498.     // So the main thing to be possibly later improved here is the case where
  6499.     // output_var is mentioned only once in the deref list (which as of v1.0.25,
  6500.     // has been partially done via the concatenation improvement, e.g. Var = %Var%Text).
  6501.     Var *arg_var[MAX_ARGS];
  6502.     VarSizeType space_needed;
  6503.     if (target_is_involved_in_source && !source_is_being_appended_to_target) // If true, output_var isn't the clipboard due to invariant: target_is_involved_in_source==false whenever output_var.Type()==VAR_CLIPBOARD.
  6504.     {
  6505.         if (ExpandArgs() != OK)
  6506.             return FAIL;
  6507.         // ARG2 now contains the dereferenced (literal) contents of the text we want to assign.
  6508.         // Therefore, calling ArgLength() is safe now too (i.e. ExpandArgs set things up for it).
  6509.         space_needed = (VarSizeType)ArgLength(2) + 1;  // +1 for the zero terminator.
  6510.     }
  6511.     else
  6512.     {
  6513.         space_needed = GetExpandedArgSize(false, arg_var); // There's at most one arg to expand in this case.
  6514.         if (space_needed == VARSIZE_ERROR)
  6515.             return FAIL;  // It will have already displayed the error.
  6516.     }
  6517.  
  6518.     // Now above has ensured that space_needed is at least 1 (it should not be zero because even
  6519.     // the empty string uses up 1 char for its zero terminator).  The below relies upon this fact.
  6520.  
  6521.     if (space_needed < 2) // Variable is being assigned the empty string (or a deref that resolves to it).
  6522.         return output_var.Assign("");  // If the var is of large capacity, this will also free its memory.
  6523.  
  6524.     if (source_is_being_appended_to_target)
  6525.     {
  6526.         if (space_needed > output_var.Capacity())
  6527.         {
  6528.             // Since expanding the size of output_var while preserving its existing contents would
  6529.             // likely be a slow operation, revert to the normal method rather than the fast-append
  6530.             // mode.  Expand the args then continue on normally to the below.
  6531.             if (ExpandArgs(space_needed, arg_var) != OK) // In this case, both params were previously calculated by GetExpandedArgSize().
  6532.                 return FAIL;
  6533.         }
  6534.         else // there's enough capacity in output_var to accept the text to be appended.
  6535.             target_is_involved_in_source = false;  // Tell the below not to consider expanding the args.
  6536.     }
  6537.  
  6538.     char *contents;
  6539.     if (target_is_involved_in_source) // output_var can't be clipboard due to invariant: target_is_involved_in_source==false whenever output_var.Type()==VAR_CLIPBOARD.
  6540.     {
  6541.         // It was already dereferenced above, so use ARG2, which points to the deref'ed contents of ARG2
  6542.         // (i.e. the data to be assigned).  I don't think there's any point to checking ARGVAR2!=NULL
  6543.         // and if so passing ARGVAR2->LengthIgnoreBinaryClip, because when we're here, ExpandArgs() will
  6544.         // have seen that it can't optimize it that way and thus it has fully expanded the variable into the buffer.
  6545.         if (!output_var.Assign(ARG2)) // Don't pass it space_needed-1 as the length because space_needed might be a conservative estimate larger than the actual length+terminator.
  6546.             return FAIL;
  6547.         if (g.AutoTrim)
  6548.         {
  6549.             contents = output_var.Contents();
  6550.             if (*contents)
  6551.             {
  6552.                 VarSizeType &var_len = output_var.Length(); // Might help perf. slightly.
  6553.                 var_len = (VarSizeType)trim(contents, var_len); // Passing length to trim() is known to greatly improve performance for long strings.
  6554.             }
  6555.         }
  6556.         return OK;
  6557.     }
  6558.  
  6559.     // Otherwise: target isn't involved in source (output_var isn't itself involved in the source data/parameter).
  6560.     // First set everything up for the operation.  If output_var is the clipboard, this
  6561.     // will prepare the clipboard for writing.  Update: If source is being appended
  6562.     // to target using the simple method, we know output_var isn't the clipboard because the
  6563.     // logic at the top of this function ensures that.
  6564.     if (!source_is_being_appended_to_target)
  6565.         if (output_var.Assign(NULL, space_needed - 1) != OK)
  6566.             return FAIL;
  6567.     // Expand Arg2 directly into the var.  Note: If output_var is the clipboard,
  6568.     // it's probably okay if the below actually writes less than the size of
  6569.     // the mem that has already been allocated for the new clipboard contents
  6570.     // That might happen due to a failure or size discrepancy between the
  6571.     // deref size-estimate and the actual deref itself:
  6572.     contents = output_var.Contents();
  6573.     // This knows not to copy the first var-ref onto itself (for when source_is_being_appended_to_target is true).
  6574.     // In addition, to reach this point, arg_var[0]'s value will already have been determined (possibly NULL)
  6575.     // by GetExpandedArgSize():
  6576.     char *one_beyond_contents_end = ExpandArg(contents, 1, arg_var[1]); // v1.0.45: Fixed arg_var[0] to be arg_var[1] (but this was only a performance issue).
  6577.     if (!one_beyond_contents_end)
  6578.         return FAIL;  // ExpandArg() will have already displayed the error.
  6579.     // Set the length explicitly rather than using space_needed because GetExpandedArgSize()
  6580.     // sometimes returns a larger size than is actually needed (e.g. for ScriptGetCursor()):
  6581.     size_t length = one_beyond_contents_end - contents - 1;
  6582.     // v1.0.25: Passing the precalculated length to trim() greatly improves performance,
  6583.     // especially for concat loops involving things like Var = %Var%String:
  6584.     output_var.Length() = (VarSizeType)(g.AutoTrim ? trim(contents, length) : length);
  6585.     return output_var.Close();  // i.e. Consider this function to be always successful unless this fails.
  6586. }
  6587.  
  6588.  
  6589.  
  6590. ResultType Line::StringReplace()
  6591. // v1.0.45: Revised to improve average-case performance and reduce memory utilization.
  6592. {
  6593.     Var &output_var = *OUTPUT_VAR;
  6594.     char *source = ARG2;
  6595.     size_t length = ArgLength(2); // Going in, it's the haystack length. Later (coming out), it's the result length.
  6596.  
  6597.     bool alternate_errorlevel = strcasestr(ARG5, "UseErrorLevel"); // This also implies replace-all.
  6598.     UINT replacement_limit = (alternate_errorlevel || StrChrAny(ARG5, "1aA")) // This must be done in a way that recognizes "AllSlow" as meaning replace-all (even though the slow method itself is obsolete).
  6599.         ? UINT_MAX : 1;
  6600.  
  6601.     // In case the strings involved are massive, free the output_var in advance of the operation to
  6602.     // reduce memory load and avoid swapping (but only if output_var isn't the same address as the input_var).
  6603.     if (output_var.Type() == VAR_NORMAL && source != output_var.Contents()) // It's compared this way in case ByRef/aliases are involved.  This will detect even them.
  6604.         output_var.Free();
  6605.     //else source and dest are the same, so can't free the dest until after the operation.
  6606.  
  6607.     // Note: The current implementation of StrReplace() should be able to handle any conceivable inputs
  6608.     // without an empty string causing an infinite loop and without going infinite due to finding the
  6609.     // search string inside of newly-inserted replace strings (e.g. replacing all occurrences
  6610.     // of b with bcb would not keep finding b in the newly inserted bcd, infinitely).
  6611.     char *dest;
  6612.     UINT found_count = StrReplace(source, ARG3, ARG4, (StringCaseSenseType)g.StringCaseSense
  6613.         , replacement_limit, -1, &dest, &length); // Length of haystack is passed to improve performance because ArgLength() can often discover it instantaneously.
  6614.  
  6615.     if (!dest) // Failure due to out of memory.
  6616.         return LineError(ERR_OUTOFMEM ERR_ABORT);
  6617.  
  6618.     if (dest != source) // StrReplace() allocated new memory rather than returning "source" to us unaltered.
  6619.     {
  6620.         // v1.0.45: Take a shortcut for performance: Hang the newly allocated memory (populated by the callee)
  6621.         // directly onto the variable, which saves a memcpy() over the old method (and possible other savings).
  6622.         // AcceptNewMem() will shrink the memory for us, via _expand(), if there's a lot of extra/unused space in it.
  6623.         output_var.AcceptNewMem(dest, (VarSizeType)length); // Tells the variable to adopt this memory as its new memory. Callee has set "length" for us.
  6624.         // Above also handles the case where output_var is VAR_CLIPBOARD.
  6625.     }
  6626.     else // StrReplace gave us back "source" unaltered because no replacements were needed.
  6627.     {
  6628.         if (output_var.Type() == VAR_NORMAL)
  6629.         {
  6630.             // Technically the following check isn't necessary because Assign() also checks for it.
  6631.             // But since StringReplace is a frequently-used command, checking it here seems worthwhile
  6632.             // to avoid calling Assign().
  6633.             if (source != output_var.Contents()) // It's compared this way in case ByRef/aliases are involved.  This will detect even them.
  6634.                 output_var.Assign(source, (VarSizeType)length); // Callee has set "length" for us.
  6635.             //else the unaltered result and output_var same the same address.  Nothing needs to be done (for
  6636.             // simplicity, not even the binary-clipboard attribute is removed if it happnes to be present).
  6637.         }
  6638.         else // output_var is of type VAR_CLIPBOARD.
  6639.             if (ARGVARRAW2->Type() != VAR_CLIPBOARD) // Arg index #1 (the second arg) is a normal var or some read-only var.
  6640.                 output_var.Assign(source, (VarSizeType)length); // Callee has set "length" for us.
  6641.             //else the unaltered result and output_var are both the clipboard.  Nothing needs to be done.
  6642.     }
  6643.  
  6644.     if (alternate_errorlevel)
  6645.         g_ErrorLevel->Assign((DWORD)found_count);
  6646.     else // Use old ErrorLevel method for backward compatibility.
  6647.         g_ErrorLevel->Assign(found_count ? ERRORLEVEL_NONE : ERRORLEVEL_ERROR);
  6648.     return OK;
  6649. }
  6650.  
  6651.  
  6652.  
  6653. ResultType Line::StringSplit(char *aArrayName, char *aInputString, char *aDelimiterList, char *aOmitList)
  6654. {
  6655.     // Make it longer than Max so that FindOrAddVar() will be able to spot and report var names
  6656.     // that are too long, either because the base-name is too long, or the name becomes too long
  6657.     // as a result of appending the array index number:
  6658.     char var_name[MAX_VAR_NAME_LENGTH + 21]; // Allow room for largest 64-bit integer, 20 chars: 18446744073709551616.
  6659.     strlcpy(var_name, aArrayName, MAX_VAR_NAME_LENGTH+1); // This prefix is copied into it only once, for performance.
  6660.     char *var_name_suffix = var_name + strlen(var_name);
  6661.  
  6662.     Var *array0;
  6663.     if (mAttribute != ATTR_NONE) // 1.0.46.10: Fixed to rely on loadtime's determination of whether ArrayName0 is truly local or global (only loadtime currently has any awareness of declarations, so the determination must be made there unless "ArrayName" itself is a dynamic variable, which seems too rare to worry about).
  6664.         array0 = (Var *)mAttribute;
  6665.     else
  6666.     {
  6667.         var_name_suffix[0] = '0';
  6668.         var_name_suffix[1] = '\0';
  6669.         // ALWAYS_PREFER_LOCAL below allows any existing local variable that matches array0's name
  6670.         // (e.g. Array0) to be given preference over creating a new global variable if the function's
  6671.         // mode is to assume globals:
  6672.         if (   !(array0 = g_script.FindOrAddVar(var_name, 0, ALWAYS_PREFER_LOCAL))   )
  6673.             return FAIL;  // It will have already displayed the error.
  6674.     }
  6675.     int always_use = array0->IsLocal() ? ALWAYS_USE_LOCAL : ALWAYS_USE_GLOBAL;
  6676.  
  6677.     if (!*aInputString) // The input variable is blank, thus there will be zero elements.
  6678.         return array0->Assign("0");  // Store the count in the 0th element.
  6679.  
  6680.     DWORD next_element_number;
  6681.     Var *next_element;
  6682.  
  6683.     if (*aDelimiterList) // The user provided a list of delimiters, so process the input variable normally.
  6684.     {
  6685.         char *contents_of_next_element, *delimiter, *new_starting_pos;
  6686.         size_t element_length;
  6687.         for (contents_of_next_element = aInputString, next_element_number = 1; ; ++next_element_number)
  6688.         {
  6689.             _ultoa(next_element_number, var_name_suffix, 10);
  6690.             // To help performance (in case the linked list of variables is huge), tell it where
  6691.             // to start the search.  Use element #0 rather than the preceding element because,
  6692.             // for example, Array19 is alphabetially less than Array2, so we can't rely on the
  6693.             // numerical ordering:
  6694.             if (   !(next_element = g_script.FindOrAddVar(var_name, 0, always_use))   )
  6695.                 return FAIL;  // It will have already displayed the error.
  6696.  
  6697.             if (delimiter = StrChrAny(contents_of_next_element, aDelimiterList)) // A delimiter was found.
  6698.             {
  6699.                 element_length = delimiter - contents_of_next_element;
  6700.                 if (*aOmitList && element_length > 0)
  6701.                 {
  6702.                     contents_of_next_element = omit_leading_any(contents_of_next_element, aOmitList, element_length);
  6703.                     element_length = delimiter - contents_of_next_element; // Update in case above changed it.
  6704.                     if (element_length)
  6705.                         element_length = omit_trailing_any(contents_of_next_element, aOmitList, delimiter - 1);
  6706.                 }
  6707.                 // If there are no chars to the left of the delim, or if they were all in the list of omitted
  6708.                 // chars, the variable will be assigned the empty string:
  6709.                 if (!next_element->Assign(contents_of_next_element, (VarSizeType)element_length))
  6710.                     return FAIL;
  6711.                 contents_of_next_element = delimiter + 1;  // Omit the delimiter since it's never included in contents.
  6712.             }
  6713.             else // the entire length of contents_of_next_element is what will be stored
  6714.             {
  6715.                 element_length = strlen(contents_of_next_element);
  6716.                 if (*aOmitList && element_length > 0)
  6717.                 {
  6718.                     new_starting_pos = omit_leading_any(contents_of_next_element, aOmitList, element_length);
  6719.                     element_length -= (new_starting_pos - contents_of_next_element); // Update in case above changed it.
  6720.                     contents_of_next_element = new_starting_pos;
  6721.                     if (element_length)
  6722.                         // If this is true, the string must contain at least one char that isn't in the list
  6723.                         // of omitted chars, otherwise omit_leading_any() would have already omitted them:
  6724.                         element_length = omit_trailing_any(contents_of_next_element, aOmitList
  6725.                             , contents_of_next_element + element_length - 1);
  6726.                 }
  6727.                 // If there are no chars to the left of the delim, or if they were all in the list of omitted
  6728.                 // chars, the variable will be assigned the empty string:
  6729.                 if (!next_element->Assign(contents_of_next_element, (VarSizeType)element_length))
  6730.                     return FAIL;
  6731.                 // This is the only way out of the loop other than critical errors:
  6732.                 return array0->Assign(next_element_number); // Store the count of how many items were stored in the array.
  6733.             }
  6734.         }
  6735.     }
  6736.  
  6737.     // Otherwise aDelimiterList is empty, so store each char of aInputString in its own array element.
  6738.     char *cp, *dp;
  6739.     for (cp = aInputString, next_element_number = 1; *cp; ++cp)
  6740.     {
  6741.         for (dp = aOmitList; *dp; ++dp)
  6742.             if (*cp == *dp) // This char is a member of the omitted list, thus it is not included in the output array.
  6743.                 break;
  6744.         if (*dp) // Omitted.
  6745.             continue;
  6746.         _ultoa(next_element_number, var_name_suffix, 10);
  6747.         if (   !(next_element = g_script.FindOrAddVar(var_name, 0, always_use))   )
  6748.             return FAIL;  // It will have already displayed the error.
  6749.         if (!next_element->Assign(cp, 1))
  6750.             return FAIL;
  6751.         ++next_element_number; // Only increment this if above didn't "continue".
  6752.     }
  6753.     return array0->Assign(next_element_number - 1); // Store the count of how many items were stored in the array.
  6754. }
  6755.  
  6756.  
  6757.  
  6758. ResultType Line::SplitPath(char *aFileSpec)
  6759. {
  6760.     Var *output_var_name = ARGVAR2;  // i.e. Param #2. Ok if NULL.
  6761.     Var *output_var_dir = ARGVAR3;  // Ok if NULL.
  6762.     Var *output_var_ext = ARGVAR4;  // Ok if NULL.
  6763.     Var *output_var_name_no_ext = ARGVAR5;  // Ok if NULL.
  6764.     Var *output_var_drive = ARGVAR6;  // Ok if NULL.
  6765.  
  6766.     // For URLs, "drive" is defined as the server name, e.g. http://somedomain.com
  6767.     char *name = "", *name_delimiter = NULL, *drive_end = NULL; // Set defaults to improve maintainability.
  6768.     char *drive = omit_leading_whitespace(aFileSpec); // i.e. whitespace is considered for everything except the drive letter or server name, so that a pathless filename can have leading whitespace.
  6769.     char *colon_double_slash = strstr(aFileSpec, "://");
  6770.  
  6771.     if (colon_double_slash) // This is a URL such as ftp://... or http://...
  6772.     {
  6773.         if (   !(drive_end = strchr(colon_double_slash + 3, '/'))   )
  6774.         {
  6775.             if (   !(drive_end = strchr(colon_double_slash + 3, '\\'))   ) // Try backslash so that things like file://C:\Folder\File.txt are supported.
  6776.                 drive_end = colon_double_slash + strlen(colon_double_slash); // Set it to the position of the zero terminator instead.
  6777.                 // And because there is no filename, leave name and name_delimiter set to their defaults.
  6778.             //else there is a backslash, e.g. file://C:\Folder\File.txt, so treat that backslash as the end of the drive name.
  6779.         }
  6780.         name_delimiter = drive_end; // Set default, to be possibly overridden below.
  6781.         // Above has set drive_end to one of the following:
  6782.         // 1) The slash that occurs to the right of the doubleslash in a URL.
  6783.         // 2) The backslash that occurs to the right of the doubleslash in a URL.
  6784.         // 3) The zero terminator if there is no slash or backslash to the right of the doubleslash.
  6785.         if (*drive_end) // A slash or backslash exists to the right of the server name.
  6786.         {
  6787.             if (*(drive_end + 1))
  6788.             {
  6789.                 // Find the rightmost slash.  At this stage, this is known to find the correct slash.
  6790.                 // In the case of a file at the root of a domain such as http://domain.com/root_file.htm,
  6791.                 // the directory consists of only the domain name, e.g. http://domain.com.  This is because
  6792.                 // the directory always the "drive letter" by design, since that is more often what the
  6793.                 // caller wants.  A script can use StringReplace to remove the drive/server portion from
  6794.                 // the directory, if desired.
  6795.                 name_delimiter = strrchr(aFileSpec, '/');
  6796.                 if (name_delimiter == colon_double_slash + 2) // To reach this point, it must have a backslash, something like file://c:\folder\file.txt
  6797.                     name_delimiter = strrchr(aFileSpec, '\\'); // Will always be found.
  6798.                 name = name_delimiter + 1; // This will be the empty string for something like http://domain.com/dir/
  6799.             }
  6800.             //else something like http://domain.com/, so leave name and name_delimiter set to their defaults.
  6801.         }
  6802.         //else something like http://domain.com, so leave name and name_delimiter set to their defaults.
  6803.     }
  6804.     else // It's not a URL, just a file specification such as c:\my folder\my file.txt, or \\server01\folder\file.txt
  6805.     {
  6806.         // Differences between _splitpath() and the method used here:
  6807.         // _splitpath() doesn't include drive in output_var_dir, it includes a trailing
  6808.         // backslash, it includes the . in the extension, it considers ":" to be a filename.
  6809.         // _splitpath(pathname, drive, dir, file, ext);
  6810.         //char sdrive[16], sdir[MAX_PATH], sname[MAX_PATH], sext[MAX_PATH];
  6811.         //_splitpath(aFileSpec, sdrive, sdir, sname, sext);
  6812.         //if (output_var_name_no_ext)
  6813.         //    output_var_name_no_ext->Assign(sname);
  6814.         //strcat(sname, sext);
  6815.         //if (output_var_name)
  6816.         //    output_var_name->Assign(sname);
  6817.         //if (output_var_dir)
  6818.         //    output_var_dir->Assign(sdir);
  6819.         //if (output_var_ext)
  6820.         //    output_var_ext->Assign(sext);
  6821.         //if (output_var_drive)
  6822.         //    output_var_drive->Assign(sdrive);
  6823.         //return OK;
  6824.  
  6825.         // Don't use _splitpath() since it supposedly doesn't handle UNC paths correctly,
  6826.         // and anyway we need more info than it provides.  Also note that it is possible
  6827.         // for a file to begin with space(s) or a dot (if created programmatically), so
  6828.         // don't trim or omit leading space unless it's known to be an absolute path.
  6829.  
  6830.         // Note that "C:Some File.txt" is a valid filename in some contexts, which the below
  6831.         // tries to take into account.  However, there will be no way for this command to
  6832.         // return a path that differentiates between "C:Some File.txt" and "C:\Some File.txt"
  6833.         // since the first backslash is not included with the returned path, even if it's
  6834.         // the root directory (i.e. "C:" is returned in both cases).  The "C:Filename"
  6835.         // convention is pretty rare, and anyway this trait can be detected via something like
  6836.         // IfInString, Filespec, :, IfNotInString, Filespec, :\, MsgBox Drive with no absolute path.
  6837.  
  6838.         // UNCs are detected with this approach so that double sets of backslashes -- which sometimes
  6839.         // occur by accident in "built filespecs" and are tolerated by the OS -- are not falsely
  6840.         // detected as UNCs.
  6841.         if (drive[0] == '\\' && drive[1] == '\\') // Relies on short-circuit evaluation order.
  6842.         {
  6843.             if (   !(drive_end = strchr(drive + 2, '\\'))   )
  6844.                 drive_end = drive + strlen(drive); // Set it to the position of the zero terminator instead.
  6845.         }
  6846.         else if (*(drive + 1) == ':') // It's an absolute path.
  6847.             // Assign letter and colon for consistency with server naming convention above.
  6848.             // i.e. so that server name and drive can be used without having to worry about
  6849.             // whether it needs a colon added or not.
  6850.             drive_end = drive + 2;
  6851.         else
  6852.         {
  6853.             // It's debatable, but it seems best to return a blank drive if a aFileSpec is a relative path.
  6854.             // rather than trying to use GetFullPathName() on a potentially non-existent file/dir.
  6855.             // _splitpath() doesn't fetch the drive letter of relative paths either.  This also reports
  6856.             // a blank drive for something like file://C:\My Folder\My File.txt, which seems too rarely
  6857.             // to justify a special mode.
  6858.             drive_end = "";
  6859.             drive = ""; // This is necessary to allow Assign() to work correctly later below, since it interprets a length of zero as "use string's entire length".
  6860.         }
  6861.  
  6862.         if (   !(name_delimiter = strrchr(aFileSpec, '\\'))   ) // No backslash.
  6863.             if (   !(name_delimiter = strrchr(aFileSpec, ':'))   ) // No colon.
  6864.                 name_delimiter = NULL; // Indicate that there is no directory.
  6865.  
  6866.         name = name_delimiter ? name_delimiter + 1 : aFileSpec; // If no delimiter, name is the entire string.
  6867.     }
  6868.  
  6869.     // The above has now set the following variables:
  6870.     // name: As an empty string or the actual name of the file, including extension.
  6871.     // name_delimiter: As NULL if there is no directory, otherwise, the end of the directory's name.
  6872.     // drive: As the start of the drive/server name, e.g. C:, \\Workstation01, http://domain.com, etc.
  6873.     // drive_end: As the position after the drive's last character, either a zero terminator, slash, or backslash.
  6874.  
  6875.     if (output_var_name && !output_var_name->Assign(name))
  6876.         return FAIL;
  6877.  
  6878.     if (output_var_dir)
  6879.     {
  6880.         if (!name_delimiter)
  6881.             output_var_dir->Assign(); // Shouldn't fail.
  6882.         else if (*name_delimiter == '\\' || *name_delimiter == '/')
  6883.         {
  6884.             if (!output_var_dir->Assign(aFileSpec, (VarSizeType)(name_delimiter - aFileSpec)))
  6885.                 return FAIL;
  6886.         }
  6887.         else // *name_delimiter == ':', e.g. "C:Some File.txt".  If aFileSpec starts with just ":",
  6888.              // the dir returned here will also start with just ":" since that's rare & illegal anyway.
  6889.             if (!output_var_dir->Assign(aFileSpec, (VarSizeType)(name_delimiter - aFileSpec + 1)))
  6890.                 return FAIL;
  6891.     }
  6892.  
  6893.     char *ext_dot = strrchr(name, '.');
  6894.     if (output_var_ext)
  6895.     {
  6896.         // Note that the OS doesn't allow filenames to end in a period.
  6897.         if (!ext_dot)
  6898.             output_var_ext->Assign();
  6899.         else
  6900.             if (!output_var_ext->Assign(ext_dot + 1)) // Can be empty string if filename ends in just a dot.
  6901.                 return FAIL;
  6902.     }
  6903.  
  6904.     if (output_var_name_no_ext && !output_var_name_no_ext->Assign(name, (VarSizeType)(ext_dot ? ext_dot - name : strlen(name))))
  6905.         return FAIL;
  6906.  
  6907.     if (output_var_drive && !output_var_drive->Assign(drive, (VarSizeType)(drive_end - drive)))
  6908.         return FAIL;
  6909.  
  6910.     return OK;
  6911. }
  6912.  
  6913.  
  6914.  
  6915. int SortWithOptions(const void *a1, const void *a2)
  6916. // Decided to just have one sort function since there are so many permutations.  The performance
  6917. // will be a little bit worse, but it seems simpler to implement and maintain.
  6918. // This function's input parameters are pointers to the elements of the array.  Snce those elements
  6919. // are themselves pointers, the input parameters are therefore pointers to pointers (handles).
  6920. {
  6921.     char *sort_item1 = *(char **)a1;
  6922.     char *sort_item2 = *(char **)a2;
  6923.     if (g_SortColumnOffset > 0)
  6924.     {
  6925.         // Adjust each string (even for numerical sort) to be the right column position,
  6926.         // or the position of its zero terminator if the column offset goes beyond its length:
  6927.         size_t length = strlen(sort_item1);
  6928.         sort_item1 += (size_t)g_SortColumnOffset > length ? length : g_SortColumnOffset;
  6929.         length = strlen(sort_item2);
  6930.         sort_item2 += (size_t)g_SortColumnOffset > length ? length : g_SortColumnOffset;
  6931.     }
  6932.     if (g_SortNumeric) // Takes precedence over g_SortCaseSensitive
  6933.     {
  6934.         // For now, assume both are numbers.  If one of them isn't, it will be sorted as a zero.
  6935.         // Thus, all non-numeric items should wind up in a sequential, unsorted group.
  6936.         // Resolve only once since parts of the ATOF() macro are inline:
  6937.         double item1_minus_2 = ATOF(sort_item1) - ATOF(sort_item2);
  6938.         if (!item1_minus_2) // Exactly equal.
  6939.             return 0;
  6940.         // Otherwise, it's either greater or less than zero:
  6941.         int result = (item1_minus_2 > 0.0) ? 1 : -1;
  6942.         return g_SortReverse ? -result : result;
  6943.     }
  6944.     // Otherwise, it's a non-numeric sort.
  6945.     // v1.0.43.03: Added support the new locale-insensitive mode.
  6946.     int result = strcmp2(sort_item1, sort_item2, g_SortCaseSensitive); // Resolve large macro only once for code size reduction.
  6947.     return g_SortReverse ? -result : result;
  6948. }
  6949.  
  6950.  
  6951.  
  6952. int SortByNakedFilename(const void *a1, const void *a2)
  6953. // See comments in prior function for details.
  6954. {
  6955.     char *sort_item1 = *(char **)a1;
  6956.     char *sort_item2 = *(char **)a2;
  6957.     char *cp;
  6958.     if (cp = strrchr(sort_item1, '\\'))  // Assign
  6959.         sort_item1 = cp + 1;
  6960.     if (cp = strrchr(sort_item2, '\\'))  // Assign
  6961.         sort_item2 = cp + 1;
  6962.     // v1.0.43.03: Added support the new locale-insensitive mode.
  6963.     int result = strcmp2(sort_item1, sort_item2, g_SortCaseSensitive); // Resolve large macro only once for code size reduction.
  6964.     return g_SortReverse ? -result : result;
  6965. }
  6966.  
  6967.  
  6968.  
  6969. struct sort_rand_type
  6970. {
  6971.     char *cp; // This must be the first member of the struct, otherwise the array trickery in PerformSort will fail.
  6972.     union
  6973.     {
  6974.         // This must be the same size in bytes as the above, which is why it's maintained as a union with
  6975.         // a char* rather than a plain int (though currently they would be the same size anyway).
  6976.         char *unused;
  6977.         int rand;
  6978.     };
  6979. };
  6980.  
  6981. int SortRandom(const void *a1, const void *a2)
  6982. // See comments in prior functions for details.
  6983. {
  6984.     return ((sort_rand_type *)a1)->rand - ((sort_rand_type *)a2)->rand;
  6985. }
  6986.  
  6987. int SortUDF(const void *a1, const void *a2)
  6988. // See comments in prior function for details.
  6989. {
  6990.     // Need to check if backup of function's variables is needed in case:
  6991.     // 1) The UDF is assigned to more than one callback, in which case the UDF could be running more than one
  6992.     //    simultantously.
  6993.     // 2) The callback is intended to be reentrant (e.g. a subclass/WindowProc that doesn't Critical).
  6994.     // 3) Script explicitly calls the UDF in addition to using it as a callback.
  6995.     //
  6996.     // See ExpandExpression() for detailed comments about the following section.
  6997.     VarBkp *var_backup = NULL;  // If needed, it will hold an array of VarBkp objects.
  6998.     int var_backup_count; // The number of items in the above array.
  6999.     if (g_SortFunc->mInstances > 0) // Backup is needed.
  7000.         if (!Var::BackupFunctionVars(*g_SortFunc, var_backup, var_backup_count)) // Out of memory.
  7001.             return 0; // Since out-of-memory is so rare, it seems justifiable not to have any error reporting and instead just say "these items are equal".
  7002.  
  7003.     // The following isn't necessary because by definition, the current thread isn't paused because it's the
  7004.     // thing that called the sort in the first place.
  7005.     //g_script.UpdateTrayIcon();
  7006.  
  7007.     char *return_value;
  7008.     g_SortFunc->mParam[0].var->Assign(*(char **)a1); // For simplicity and due to extreme rarity, parameters beyond
  7009.     g_SortFunc->mParam[1].var->Assign(*(char **)a2); // the first 2 aren't populated even if they have default values.
  7010.     if (g_SortFunc->mParamCount > 2)
  7011.         g_SortFunc->mParam[2].var->Assign((__int64)(*(char **)a2 - *(char **)a1)); // __int64 to allow for a list greater than 2 GB, though that is currently impossible.
  7012.     g_SortFunc->Call(return_value); // Call the UDF.
  7013.  
  7014.     // MUST handle return_value BEFORE calling FreeAndRestoreFunctionVars() because return_value might be
  7015.     // the contents of one of the function's local variables (which are about to be free'd).
  7016.     int returned_int;
  7017.     if (*return_value) // No need to check the following because they're implied for *return_value!=0: result != EARLY_EXIT && result != FAIL;
  7018.     {
  7019.         // Using float vs. int makes sort up to 46% slower, so decided to use int. Must use ATOI64 vs. ATOI
  7020.         // because otherwise a negative might overflow/wrap into a positive (at least with the MSVC++
  7021.         // implementation of ATOI).
  7022.         // ATOI64()'s implementation (and probably any/all others?) truncates any decimal portion;
  7023.         // e.g. 0.8 and 0.3 both yield 0.
  7024.         __int64 i64 = ATOI64(return_value);
  7025.         if (i64 > 0)  // Maybe there's a faster/better way to do these checks. Can't simply typecast to an int because some large positives wrap into negative, maybe vice versa.
  7026.             returned_int = 1;
  7027.         else if (i64 < 0)
  7028.             returned_int = -1;
  7029.         else
  7030.             returned_int = 0;
  7031.     }
  7032.     else
  7033.         returned_int = 0;
  7034.  
  7035.     Var::FreeAndRestoreFunctionVars(*g_SortFunc, var_backup, var_backup_count);
  7036.     return returned_int;
  7037. }
  7038.  
  7039.  
  7040.  
  7041. ResultType Line::PerformSort(char *aContents, char *aOptions)
  7042. // Caller must ensure that aContents is modifiable (ArgMustBeDereferenced() currently ensures this) because
  7043. // not only does this function modify it, it also needs to store its result back into output_var in a way
  7044. // that requires that output_var not be at the same address as the contents that were sorted.
  7045. // It seems best to treat ACT_SORT's var to be an input vs. output var because if
  7046. // it's an environment variable or the clipboard, the input variable handler will
  7047. // automatically resolve it to be ARG1 (i.e. put its contents into the deref buf).
  7048. // This is especially necessary if the clipboard contains files, in which case
  7049. // output_var->Get(), not Contents(),  must be used to resolve the filenames into text.
  7050. // And on average, using the deref buffer for this operation will not be wasteful in
  7051. // terms of expanding it unnecessarily, because usually the contents will have been
  7052. // already (or will soon be) in the deref buffer as a result of commands before
  7053. // or after the Sort command in the script.
  7054. {
  7055.     // Set defaults in case of early goto:
  7056.     char *mem_to_free = NULL;
  7057.     Func *sort_func_orig = g_SortFunc; // Because UDFs can be interrupted by other threads -- and because UDFs can themselves call Sort with some other UDF (unlikely to be sure) -- backup & restore original g_SortFunc so that the "collapsing in reverse order" behavior will automatically ensure proper operation.
  7058.     g_SortFunc = NULL; // Now that original has been saved above, reset to detect whether THIS sort uses a UDF.
  7059.     ResultType result_to_return = OK;
  7060.     DWORD ErrorLevel = -1; // Use -1 to mean "don't change/set ErrorLevel".
  7061.  
  7062.     // Resolve options.  First set defaults for options:
  7063.     char delimiter = '\n';
  7064.     g_SortCaseSensitive = SCS_INSENSITIVE;
  7065.     g_SortNumeric = false;
  7066.     g_SortReverse = false;
  7067.     g_SortColumnOffset = 0;
  7068.     bool trailing_delimiter_indicates_trailing_blank_item = false, terminate_last_item_with_delimiter = false
  7069.         , trailing_crlf_added_temporarily = false, sort_by_naked_filename = false, sort_random = false
  7070.         , omit_dupes = false;
  7071.     char *cp, *cp_end;
  7072.  
  7073.     for (cp = aOptions; *cp; ++cp)
  7074.     {
  7075.         switch(toupper(*cp))
  7076.         {
  7077.         case 'C':
  7078.             if (toupper(cp[1]) == 'L') // v1.0.43.03: Locale-insensitive mode, which probably performs considerably worse.
  7079.             {
  7080.                 ++cp;
  7081.                 g_SortCaseSensitive = SCS_INSENSITIVE_LOCALE;
  7082.             }
  7083.             else
  7084.                 g_SortCaseSensitive = SCS_SENSITIVE;
  7085.             break;
  7086.         case 'D':
  7087.             if (!cp[1]) // Avoids out-of-bounds when the loop's own ++cp is done.
  7088.                 break;
  7089.             ++cp;
  7090.             if (*cp)
  7091.                 delimiter = *cp;
  7092.             break;
  7093.         case 'F': // v1.0.47: Support a callback function to extend flexibility.
  7094.             // Decided not to set ErrorLevel here because omit-dupes already uses it, and the code/docs
  7095.             // complexity of having one take precedence over the other didn't seem worth it given rarity
  7096.             // of errors and rarity of UDF use.
  7097.             cp = omit_leading_whitespace(cp + 1); // Point it to the function's name.
  7098.             if (   !(cp_end = StrChrAny(cp, " \t"))   ) // Find space or tab, if any.
  7099.                 cp_end = cp + strlen(cp); // Point it to the terminator instead.
  7100.             if (   !(g_SortFunc = g_script.FindFunc(cp, cp_end - cp))   )
  7101.                 goto end; // For simplicity, just abort the sort.
  7102.             // To improve callback performance, ensure there are no ByRef parameters (for simplicity:
  7103.             // not even ones that have default values) among the first two parameters.  This avoids the
  7104.             // need to ensure formal parameters are non-aliases each time the callback is called.
  7105.             if (g_SortFunc->mIsBuiltIn || g_SortFunc->mParamCount < 2 // This validation is relied upon at a later stage.
  7106.                 || g_SortFunc->mParamCount > 3  // Reserve 4-or-more parameters for possible future use (to avoid breaking existing scripts if such features are ever added).
  7107.                 || g_SortFunc->mParam[0].is_byref || g_SortFunc->mParam[1].is_byref) // Relies on short-circuit boolean order.
  7108.                 goto end; // For simplicity, just abort the sort.
  7109.             // Otherwise, the function meets the minimum contraints (though for simplicity, optional parameters
  7110.             // (default values), if any, aren't populated).
  7111.             // Fix for v1.0.47.05: The following line now subtracts 1 in case *cp_end=='\0'; otherwise the
  7112.             // loop's ++cp would go beyond the terminator when there are no more options.
  7113.             cp = cp_end - 1; // In the next interation (which also does a ++cp), resume looking for options after the function's name.
  7114.             break;
  7115.         case 'N':
  7116.             g_SortNumeric = true;
  7117.             break;
  7118.         case 'P':
  7119.             // Use atoi() vs. ATOI() to avoid interpreting something like 0x01C as hex
  7120.             // when in fact the C was meant to be an option letter:
  7121.             g_SortColumnOffset = atoi(cp + 1);
  7122.             if (g_SortColumnOffset < 1)
  7123.                 g_SortColumnOffset = 1;
  7124.             --g_SortColumnOffset;  // Convert to zero-based.
  7125.             break;
  7126.         case 'R':
  7127.             if (!strnicmp(cp, "Random", 6))
  7128.             {
  7129.                 sort_random = true;
  7130.                 cp += 5; // Point it to the last char so that the loop's ++cp will point to the character after it.
  7131.             }
  7132.             else
  7133.                 g_SortReverse = true;
  7134.             break;
  7135.         case 'U':  // Unique.
  7136.             omit_dupes = true;
  7137.             ErrorLevel = 0; // Set default dupe-count to 0 in case of early return.
  7138.             break;
  7139.         case 'Z':
  7140.             // By setting this to true, the final item in the list, if it ends in a delimiter,
  7141.             // is considered to be followed by a blank item:
  7142.             trailing_delimiter_indicates_trailing_blank_item = true;
  7143.             break;
  7144.         case '\\':
  7145.             sort_by_naked_filename = true;
  7146.         }
  7147.     }
  7148.  
  7149.     // Check for early return only after parsing options in case an option that sets ErrorLevel is present:
  7150.     if (!*aContents) // Variable is empty, nothing to sort.
  7151.         goto end;
  7152.     Var &output_var = *OUTPUT_VAR; // The input var (ARG1) is also the output var in this case.
  7153.     // Do nothing for reserved variables, since most of them are read-only and besides, none
  7154.     // of them (realistically) should ever need sorting:
  7155.     if (VAR_IS_READONLY(output_var)) // output_var can be a reserved variable because it's not marked as an output-var by ArgIsVar() [since it has a dual purpose as an input-var].
  7156.         goto end;
  7157.  
  7158.     // size_t helps performance and should be plenty of capacity for many years of advancement.
  7159.     // In addition, things like realloc() can't accept anything larger than size_t anyway,
  7160.     // so there's no point making this 64-bit until size_t itself becomes 64-bit (it already is on some compilers?).
  7161.     size_t item_count;
  7162.  
  7163.     // Explicitly calculate the length in case it's the clipboard or an environment var.
  7164.     // (in which case Length() does not contain the current length).  While calculating
  7165.     // the length, also check how many delimiters are present:
  7166.     for (item_count = 1, cp = aContents; *cp; ++cp)  // Start at 1 since item_count is delimiter_count+1
  7167.         if (*cp == delimiter)
  7168.             ++item_count;
  7169.     size_t aContents_length = cp - aContents;
  7170.  
  7171.     // If the last character in the unsorted list is a delimiter then technically the final item
  7172.     // in the list is a blank item.  However, if the options specify not to allow that, don't count that
  7173.     // blank item as an actual item (i.e. omit it from the list):
  7174.     if (!trailing_delimiter_indicates_trailing_blank_item && cp > aContents && cp[-1] == delimiter)
  7175.     {
  7176.         terminate_last_item_with_delimiter = true; // Have a later stage add the delimiter to the end of the sorted list so that the length and format of the sorted list is the same as the unsorted list.
  7177.         --item_count; // Indicate that the blank item at the end of the list isn't being counted as an item.
  7178.     }
  7179.     else // The final character isn't a delimiter or it is but there's a blank item to its right.  Either way, the following is necessary (veriifed correct).
  7180.     {
  7181.         if (delimiter == '\n')
  7182.         {
  7183.             char *first_delimiter = strchr(aContents, delimiter);
  7184.             if (first_delimiter && first_delimiter > aContents && first_delimiter[-1] == '\r')
  7185.             {
  7186.                 // v1.0.47.05:
  7187.                 // Here the delimiter is effectively CRLF vs. LF.  Therefore, signal a later section to append
  7188.                 // an extra CRLF to simplify the code and fix bugs that existed prior to v1.0.47.05.
  7189.                 // One such bug is that sorting "x`r`nx" in unique mode would previously produce two
  7190.                 // x's rather than one because the first x is seen to have a `r to its right but the
  7191.                 // second lacks it (due to the CRLF delimiter being simulated via LF-only).
  7192.                 //
  7193.                 // OLD/OBSOLETE comment from a section that was removed because it's now handled by this section:
  7194.                 // Check if special handling is needed due to the following situation:
  7195.                 // Delimiter is LF but the contents are lines delimited by CRLF, not just LF
  7196.                 // and the original/unsorted list's last item was not terminated by an
  7197.                 // "allowed delimiter".  The symptoms of this are that after the sort, the
  7198.                 // last item will end in \r when it should end in no delimiter at all.
  7199.                 // This happens pretty often, such as when the clipboard contains files.
  7200.                 // In the future, an option letter can be added to turn off this workaround,
  7201.                 // but it seems unlikely that anyone would ever want to.
  7202.                 trailing_crlf_added_temporarily = true;
  7203.                 terminate_last_item_with_delimiter = true; // This is done to "fool" later sections into thinking the list ends in a CRLF that doesn't have a blank item to the right, which in turn simplifies the logic and/or makes it more understandable.
  7204.             }
  7205.         }
  7206.     }
  7207.  
  7208.     if (item_count == 1) // 1 item is already sorted, and no dupes are possible.
  7209.     {
  7210.         // Put the exact contents back into the output_var, which is necessary in case
  7211.         // the variable was an environment variable or the clipboard-containing-files,
  7212.         // since in those cases we want the behavior to be consistent regardless of
  7213.         // whether there's only 1 item or more than one:
  7214.         // Clipboard-contains-files: The single file should be translated into its
  7215.         // text equivalent.  Var was an environment variable: the corresponding script
  7216.         // variable should be assigned the contents, so it will basically "take over"
  7217.         // for the environment variable.
  7218.         result_to_return = output_var.Assign(aContents, (VarSizeType)aContents_length);
  7219.         goto end;
  7220.     }
  7221.  
  7222.     // v1.0.47.05: It simplifies the code a lot to allocate and/or improves understandability to allocate
  7223.     // memory for trailing_crlf_added_temporarily even though technically it's done only to make room to
  7224.     // append the extra CRLF at the end.
  7225.     if (g_SortFunc || trailing_crlf_added_temporarily) // Do this here rather than earlier with the options parsing in case the function-option is present twice (unlikely, but it would be a memory leak due to strdup below).  Doing it here also avoids allocating if it isn't necessary.
  7226.     {
  7227.         // When g_SortFunc!=NULL, the copy of the string is needed because an earlier stage has ensured that
  7228.         // aContents is in the deref buffer, but that deref buffer is about to be overwritten by the
  7229.         // execution of the script's UDF body.
  7230.         if (   !(mem_to_free = (char *)malloc(aContents_length + 3))   ) // +1 for terminator and +2 in case of trailing_crlf_added_temporarily.
  7231.         {
  7232.             result_to_return = LineError(ERR_OUTOFMEM);  // Short msg. since so rare.
  7233.             goto end;
  7234.         }
  7235.         memcpy(mem_to_free, aContents, aContents_length + 1); // memcpy() usually benches a little faster than strcpy().
  7236.         aContents = mem_to_free;
  7237.         if (trailing_crlf_added_temporarily)
  7238.         {
  7239.             // Must be added early so that the next stage will terminate at the \n, leaving the \r as
  7240.             // the ending character in this item.
  7241.             strcpy(aContents + aContents_length, "\r\n");
  7242.             aContents_length += 2;
  7243.         }
  7244.     }
  7245.  
  7246.     // Create the array of pointers that points into aContents to each delimited item.
  7247.     // Use item_count + 1 to allow space for the last (blank) item in case
  7248.     // trailing_delimiter_indicates_trailing_blank_item is false:
  7249.     int unit_size = sort_random ? 2 : 1;
  7250.     size_t item_size = unit_size * sizeof(char *);
  7251.     char **item = (char **)malloc((item_count + 1) * item_size);
  7252.     if (!item)
  7253.     {
  7254.         result_to_return = LineError(ERR_OUTOFMEM);  // Short msg. since so rare.
  7255.         goto end;
  7256.     }
  7257.  
  7258.     // If sort_random is in effect, the above has created an array twice the normal size.
  7259.     // This allows the random numbers to be interleaved inside the array as though it
  7260.     // were an array consisting of sort_rand_type (which it actually is when viewed that way).
  7261.     // Because of this, the array should be accessed through pointer addition rather than
  7262.     // indexing via [].
  7263.  
  7264.     // Scan aContents and do the following:
  7265.     // 1) Replace each delimiter with a terminator so that the individual items can be seen
  7266.     //    as real strings by the SortWithOptions() and when copying the sorted results back
  7267.     //    into output_vav.  It is safe to change aContents in this way because
  7268.     //    ArgMustBeDereferenced() has ensured that those contents are in the deref buffer.
  7269.     // 2) Store a marker/pointer to each item (string) in aContents so that we know where
  7270.     //    each item begins for sorting and recopying purposes.
  7271.     char **item_curr = item; // i.e. Don't use [] indexing for the reason in the paragraph previous to above.
  7272.     for (item_count = 0, cp = *item_curr = aContents; *cp; ++cp)
  7273.     {
  7274.         if (*cp == delimiter)  // Each delimiter char becomes the terminator of the previous key phrase.
  7275.         {
  7276.             *cp = '\0';  // Terminate the item that appears before this delimiter.
  7277.             ++item_count;
  7278.             if (sort_random)
  7279.                 *(item_curr + 1) = (char *)(size_t)genrand_int31(); // i.e. the randoms are in the odd fields, the pointers in the even.
  7280.                 // For the above:
  7281.                 // I don't know the exact reasons, but using genrand_int31() is much more random than
  7282.                 // using genrand_int32() in this case.  Perhaps it is some kind of statistical/cyclical
  7283.                 // anomaly in the random number generator.  Or perhaps it's something to do with integer
  7284.                 // underflow/overflow in SortRandom().  In any case, the problem can be proven via the
  7285.                 // following script, which shows a sharply non-random distribution when genrand_int32()
  7286.                 // is used:
  7287.                 //count = 0
  7288.                 //Loop 10000
  7289.                 //{
  7290.                 //    var = 1`n2`n3`n4`n5`n
  7291.                 //    Sort, Var, Random
  7292.                 //    StringLeft, Var1, Var, 1
  7293.                 //    if Var1 = 5  ; Change this value to 1 to see the opposite problem.
  7294.                 //        count += 1
  7295.                 //}
  7296.                 //Msgbox %count%
  7297.                 //
  7298.                 // I e-mailed the author about this sometime around/prior to 12/1/04 but never got a response.
  7299.             item_curr += unit_size; // i.e. Don't use [] indexing for the reason described above.
  7300.             *item_curr = cp + 1; // Make a pointer to the next item's place in aContents.
  7301.         }
  7302.     }
  7303.     // The above reset the count to 0 and recounted it.  So now re-add the last item to the count unless it was
  7304.     // disqualified earlier. Verified correct:
  7305.     if (!terminate_last_item_with_delimiter) // i.e. either trailing_delimiter_indicates_trailing_blank_item==true OR the final character isn't a delimiter. Either way the final item needs to be added.
  7306.     {
  7307.         ++item_count;
  7308.         if (sort_random) // Provide a random number for the last item.
  7309.             *(item_curr + 1) = (char *)(size_t)genrand_int31(); // i.e. the randoms are in the odd fields, the pointers in the even.
  7310.     }
  7311.     else // Since the final item is not included in the count, point item_curr to the one before the last, for use below.
  7312.         item_curr -= unit_size;
  7313.  
  7314.     // Now aContents has been divided up based on delimiter.  Sort the array of pointers
  7315.     // so that they indicate the correct ordering to copy aContents into output_var:
  7316.     if (g_SortFunc) // Takes precedence other sorting methods.
  7317.         qsort((void *)item, item_count, item_size, SortUDF);
  7318.     else if (sort_random) // Takes precedence over all remaining options.
  7319.         qsort((void *)item, item_count, item_size, SortRandom);
  7320.     else
  7321.         qsort((void *)item, item_count, item_size, sort_by_naked_filename ? SortByNakedFilename : SortWithOptions);
  7322.  
  7323.     // Copy the sorted pointers back into output_var, which might not already be sized correctly
  7324.     // if it's the clipboard or it was an environment variable when it came in as the input.
  7325.     // If output_var is the clipboard, this call will set up the clipboard for writing:
  7326.     if (output_var.Assign(NULL, (VarSizeType)aContents_length) != OK) // Might fail due to clipboard problem.
  7327.     {
  7328.         result_to_return = FAIL;
  7329.         goto end;
  7330.     }
  7331.  
  7332.     // Set default in case original last item is still the last item, or if last item was omitted due to being a dupe:
  7333.     size_t i, item_count_minus_1 = item_count - 1;
  7334.     DWORD omit_dupe_count = 0;
  7335.     bool keep_this_item;
  7336.     char *source, *dest;
  7337.     char *item_prev = NULL;
  7338.  
  7339.     // Copy the sorted result back into output_var.  Do all except the last item, since the last
  7340.     // item gets special treatment depending on the options that were specified.  The call to
  7341.     // output_var->Contents() below should never fail due to the above having prepped it:
  7342.     item_curr = item; // i.e. Don't use [] indexing for the reason described higher above (same applies to item += unit_size below).
  7343.     for (dest = output_var.Contents(), i = 0; i < item_count; ++i, item_curr += unit_size)
  7344.     {
  7345.         keep_this_item = true;  // Set default.
  7346.         if (omit_dupes && item_prev)
  7347.         {
  7348.             // Update to the comment below: Exact dupes will still be removed when sort_by_naked_filename
  7349.             // or g_SortColumnOffset is in effect because duplicate lines would still be adjacent to
  7350.             // each other even in these modes.  There doesn't appear to be any exceptions, even if
  7351.             // some items in the list are sorted as blanks due to being shorter than the specified 
  7352.             // g_SortColumnOffset.
  7353.             // As documented, special dupe-checking modes are not offered when sort_by_naked_filename
  7354.             // is in effect, or g_SortColumnOffset is greater than 1.  That's because the need for such
  7355.             // a thing seems too rare (and the result too strange) to justify the extra code size.
  7356.             // However, adjacent dupes are still removed when any of the above modes are in effect,
  7357.             // or when the "random" mode is in effect.  This might have some usefulness; for example,
  7358.             // if a list of songs is sorted in random order, but it contains "favorite" songs listed twice,
  7359.             // the dupe-removal feature would remove duplicate songs if they happen to be sorted
  7360.             // to lie adjacent to each other, which would be useful to prevent the same song from
  7361.             // playing twice in a row.
  7362.             if (g_SortNumeric && !g_SortColumnOffset)
  7363.                 // if g_SortColumnOffset is zero, fall back to the normal dupe checking in case its
  7364.                 // ever useful to anyone.  This is done because numbers in an offset column are not supported
  7365.                 // since the extra code size doensn't seem justified given the rarity of the need.
  7366.                 keep_this_item = (ATOF(*item_curr) != ATOF(item_prev)); // ATOF() ignores any trailing \r in CRLF mode, so no extra logic is needed for that.
  7367.             else
  7368.                 keep_this_item = strcmp2(*item_curr, item_prev, g_SortCaseSensitive); // v1.0.43.03: Added support for locale-insensitive mode.
  7369.                 // Permutations of sorting case sensitive vs. eliminating duplicates based on case sensitivity:
  7370.                 // 1) Sort is not case sens, but dupes are: Won't work because sort didn't necessarily put
  7371.                 //    same-case dupes adjacent to each other.
  7372.                 // 2) Converse: probably not reliable because there could be unrelated items in between
  7373.                 //    two strings that are duplicates but weren't sorted adjacently due to their case.
  7374.                 // 3) Both are case sensitive: seems okay
  7375.                 // 4) Both are not case sensitive: seems okay
  7376.                 //
  7377.                 // In light of the above, using the g_SortCaseSensitive flag to control the behavior of
  7378.                 // both sorting and dupe-removal seems best.
  7379.         }
  7380.         if (keep_this_item)
  7381.         {
  7382.             for (source = *item_curr; *source;)
  7383.                 *dest++ = *source++;
  7384.             // If we're at the last item and the original list's last item had a terminating delimiter
  7385.             // and the specified options said to treat it not as a delimiter but as a final char of sorts,
  7386.             // include it after the item that is now last so that the overall layout is the same:
  7387.             if (i < item_count_minus_1 || terminate_last_item_with_delimiter)
  7388.                 *dest++ = delimiter;  // Put each item's delimiter back in so that format is the same as the original.
  7389.             item_prev = *item_curr; // Since the item just processed above isn't a dupe, save this item to compare against the next item.
  7390.         }
  7391.         else // This item is a duplicate of the previous item.
  7392.         {
  7393.             ++omit_dupe_count; // But don't change the value of item_prev.
  7394.             // v1.0.47.05: The following section fixes the fact that the "unique" option would sometimes leave
  7395.             // a trailing delimiter at the end of the sorted list.  For example, sorting "x|x" in unique mode
  7396.             // would formerly produce "x|":
  7397.             if (i == item_count_minus_1 && !terminate_last_item_with_delimiter) // This is the last item and its being omitted, so remove the previous item's trailing delimiter (unless a trailing delimiter is mandatory).
  7398.                 --dest; // Remove the previous item's trailing delimiter there's nothing for it to delimit due to omission of this duplicate.
  7399.         }
  7400.     } // for()
  7401.     free(item); // Free the index/pointer list used for the sort.
  7402.  
  7403.     // Terminate the variable's contents.
  7404.     if (trailing_crlf_added_temporarily) // Remove the CRLF only after its presence was used above to simplify the code by reducing the number of types/cases.
  7405.     {
  7406.         dest[-2] = '\0';
  7407.         output_var.Length() -= 2;
  7408.     }
  7409.     else
  7410.         *dest = '\0';
  7411.  
  7412.     if (omit_dupes)
  7413.     {
  7414.         if (omit_dupe_count) // Update the length to actual whenever at least one dupe was omitted.
  7415.         {
  7416.             output_var.Length() = (VarSizeType)strlen(output_var.Contents());
  7417.             ErrorLevel = omit_dupe_count; // Override the 0 set earlier.
  7418.         }
  7419.     }
  7420.     //else it is not necessary to set output_var.Length() here because its length hasn't changed
  7421.     // since it was originally set by the above call "output_var.Assign(NULL..."
  7422.  
  7423.     result_to_return = output_var.Close();  // Close in case it's the clipboard.
  7424.  
  7425. end:
  7426.     if (ErrorLevel != -1) // A change to ErrorLevel is desired.  Compare directly to -1 due to unsigned.
  7427.         g_ErrorLevel->Assign(ErrorLevel); // ErrorLevel is set only when dupe-mode is in effect.
  7428.     if (mem_to_free)
  7429.         free(mem_to_free);
  7430.     g_SortFunc = sort_func_orig;
  7431.     return result_to_return;
  7432. }
  7433.  
  7434.  
  7435.  
  7436. ResultType Line::GetKeyJoyState(char *aKeyName, char *aOption)
  7437. // Keep this in sync with FUNC_GETKEYSTATE.
  7438. {
  7439.     Var &output_var = *OUTPUT_VAR;
  7440.     JoyControls joy;
  7441.     int joystick_id;
  7442.     vk_type vk = TextToVK(aKeyName);
  7443.     if (!vk)
  7444.     {
  7445.         if (   !(joy = (JoyControls)ConvertJoy(aKeyName, &joystick_id))   )
  7446.             return output_var.Assign("");
  7447.         // Since the above didn't return, joy contains a valid joystick button/control ID.
  7448.         // Caller needs a token with a buffer of at least this size:
  7449.         char buf[MAX_FORMATTED_NUMBER_LENGTH + 1];
  7450.         ExprTokenType token;
  7451.         token.symbol = SYM_STRING; // These must be set as defaults for ScriptGetJoyState().
  7452.         token.marker = buf;        //
  7453.         ScriptGetJoyState(joy, joystick_id, token, false);
  7454.         // Above: ScriptGetJoyState() returns FAIL and sets output_var to be blank if the result is
  7455.         // indeterminate or there was a problem reading the joystick.  But we don't want such a failure
  7456.         // to be considered a "critical failure" that will exit the current quasi-thread, so its result
  7457.         // is discarded.
  7458.         return output_var.Assign(token); // Write the result based on whether the token is a string or number.
  7459.     }
  7460.     // Otherwise: There is a virtual key (not a joystick control).
  7461.     KeyStateTypes key_state_type;
  7462.     switch (toupper(*aOption))
  7463.     {
  7464.     case 'T': key_state_type = KEYSTATE_TOGGLE; break; // Whether a toggleable key such as CapsLock is currently turned on.
  7465.     case 'P': key_state_type = KEYSTATE_PHYSICAL; break; // Physical state of key.
  7466.     default: key_state_type = KEYSTATE_LOGICAL;
  7467.     }
  7468.     return output_var.Assign(ScriptGetKeyState(vk, key_state_type) ? "D" : "U");
  7469. }
  7470.  
  7471.  
  7472.  
  7473. ResultType Line::DriveSpace(char *aPath, bool aGetFreeSpace)
  7474. // Because of NTFS's ability to mount volumes into a directory, a path might not necessarily
  7475. // have the same amount of free space as its root drive.  However, I'm not sure if this
  7476. // method here actually takes that into account.
  7477. {
  7478.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  7479.     OUTPUT_VAR->Assign(); // Init to empty string regardless of whether we succeed here.
  7480.  
  7481.     if (!aPath || !*aPath) return OK;  // Let ErrorLevel tell the story.  Below relies on this check.
  7482.  
  7483.     char buf[MAX_PATH + 1];  // +1 to allow appending of backslash.
  7484.     strlcpy(buf, aPath, sizeof(buf));
  7485.     size_t length = strlen(buf);
  7486.     if (buf[length - 1] != '\\') // Trailing backslash is present, which some of the API calls below don't like.
  7487.     {
  7488.         if (length + 1 >= sizeof(buf)) // No room to fix it.
  7489.             return OK; // Let ErrorLevel tell the story.
  7490.         buf[length++] = '\\';
  7491.         buf[length] = '\0';
  7492.     }
  7493.  
  7494.     SetErrorMode(SEM_FAILCRITICALERRORS); // If target drive is a floppy, this avoids a dialog prompting to insert a disk.
  7495.  
  7496.     // The program won't launch at all on Win95a (original Win95) unless the function address is resolved
  7497.     // at runtime:
  7498.     typedef BOOL (WINAPI *GetDiskFreeSpaceExType)(LPCTSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER);
  7499.     static GetDiskFreeSpaceExType MyGetDiskFreeSpaceEx =
  7500.         (GetDiskFreeSpaceExType)GetProcAddress(GetModuleHandle("kernel32"), "GetDiskFreeSpaceExA");
  7501.  
  7502.     // MSDN: "The GetDiskFreeSpaceEx function returns correct values for all volumes, including those
  7503.     // that are greater than 2 gigabytes."
  7504.     __int64 free_space;
  7505.     if (MyGetDiskFreeSpaceEx)  // Function is available (unpatched Win95 and WinNT might not have it).
  7506.     {
  7507.         ULARGE_INTEGER total, free, used;
  7508.         if (!MyGetDiskFreeSpaceEx(buf, &free, &total, &used))
  7509.             return OK; // Let ErrorLevel tell the story.
  7510.         // Casting this way allows sizes of up to 2,097,152 gigabytes:
  7511.         free_space = (__int64)((unsigned __int64)(aGetFreeSpace ? free.QuadPart : total.QuadPart)
  7512.             / (1024*1024));
  7513.     }
  7514.     else // For unpatched versions of Win95/NT4, fall back to compatibility mode.
  7515.     {
  7516.         DWORD sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters;
  7517.         if (!GetDiskFreeSpace(buf, §ors_per_cluster, &bytes_per_sector, &free_clusters, &total_clusters))
  7518.             return OK; // Let ErrorLevel tell the story.
  7519.         free_space = (__int64)((unsigned __int64)((aGetFreeSpace ? free_clusters : total_clusters)
  7520.             * sectors_per_cluster * bytes_per_sector) / (1024*1024));
  7521.     }
  7522.  
  7523.     g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  7524.     return OUTPUT_VAR->Assign(free_space);
  7525. }
  7526.  
  7527.  
  7528.  
  7529. ResultType Line::Drive(char *aCmd, char *aValue, char *aValue2) // aValue not aValue1, for use with a shared macro.
  7530. {
  7531.     DriveCmds drive_cmd = ConvertDriveCmd(aCmd);
  7532.  
  7533.     char path[MAX_PATH + 1];  // +1 to allow room for trailing backslash in case it needs to be added.
  7534.     size_t path_length;
  7535.  
  7536.     // Notes about the below macro:
  7537.     // - It adds a backslash to the contents of the path variable because certain API calls or OS versions
  7538.     //   might require it.
  7539.     // - It is used by both Drive() and DriveGet().
  7540.     // - Leave space for the backslash in case its needed.
  7541.     #define DRIVE_SET_PATH \
  7542.         strlcpy(path, aValue, sizeof(path) - 1);\
  7543.         path_length = strlen(path);\
  7544.         if (path_length && path[path_length - 1] != '\\')\
  7545.             path[path_length++] = '\\';
  7546.  
  7547.     switch(drive_cmd)
  7548.     {
  7549.     case DRIVE_CMD_INVALID:
  7550.         // Since command names are validated at load-time, this only happens if the command name
  7551.         // was contained in a variable reference.  Since that is very rare, just set ErrorLevel
  7552.         // and return:
  7553.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  7554.  
  7555.     case DRIVE_CMD_LOCK:
  7556.     case DRIVE_CMD_UNLOCK:
  7557.         return g_ErrorLevel->Assign(DriveLock(*aValue, drive_cmd == DRIVE_CMD_LOCK) ? ERRORLEVEL_NONE : ERRORLEVEL_ERROR); // Indicate success or failure.
  7558.  
  7559.     case DRIVE_CMD_EJECT:
  7560.         // Don't do DRIVE_SET_PATH in this case since trailing backslash might prevent it from
  7561.         // working on some OSes.
  7562.         // It seems best not to do the below check since:
  7563.         // 1) aValue usually lacks a trailing backslash so that it will work correctly with "open c: type cdaudio".
  7564.         //    That lack might prevent DriveGetType() from working on some OSes.
  7565.         // 2) It's conceivable that tray eject/retract might work on certain types of drives even though
  7566.         //    they aren't of type DRIVE_CDROM.
  7567.         // 3) One or both of the calls to mciSendString() will simply fail if the drive isn't of the right type.
  7568.         //if (GetDriveType(aValue) != DRIVE_CDROM) // Testing reveals that the below method does not work on Network CD/DVD drives.
  7569.         //    return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  7570.         char mci_string[256];
  7571.         MCIERROR error;
  7572.         // Note: The following comment is obsolete because research of MSDN indicates that there is no way
  7573.         // not to wait when the tray must be physically opened or closed, at least on Windows XP.  Omitting
  7574.         // the word "wait" from both "close cd wait" and "set cd door open/closed wait" does not help, nor
  7575.         // does replacing wait with the word notify in "set cdaudio door open/closed wait".
  7576.         // The word "wait" is always specified with these operations to ensure consistent behavior across
  7577.         // all OSes (on the off-chance that the absence of "wait" really avoids waiting on Win9x or future
  7578.         // OSes, or perhaps under certain conditions or for certain types of drives).  See above comment
  7579.         // for details.
  7580.         if (!*aValue) // When drive is omitted, operate upon default CD/DVD drive.
  7581.         {
  7582.             snprintf(mci_string, sizeof(mci_string), "set cdaudio door %s wait", ATOI(aValue2) == 1 ? "closed" : "open");
  7583.             error = mciSendString(mci_string, NULL, 0, NULL); // Open or close the tray.
  7584.             return g_ErrorLevel->Assign(error ? ERRORLEVEL_ERROR : ERRORLEVEL_NONE); // Indicate success or failure.
  7585.         }
  7586.         snprintf(mci_string, sizeof(mci_string), "open %s type cdaudio alias cd wait shareable", aValue);
  7587.         if (mciSendString(mci_string, NULL, 0, NULL)) // Error.
  7588.             return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  7589.         snprintf(mci_string, sizeof(mci_string), "set cd door %s wait", ATOI(aValue2) == 1 ? "closed" : "open");
  7590.         error = mciSendString(mci_string, NULL, 0, NULL); // Open or close the tray.
  7591.         mciSendString("close cd wait", NULL, 0, NULL);
  7592.         return g_ErrorLevel->Assign(error ? ERRORLEVEL_ERROR : ERRORLEVEL_NONE); // Indicate success or failure.
  7593.  
  7594.     case DRIVE_CMD_LABEL: // Note that is is possible and allowed for the new label to be blank.
  7595.         DRIVE_SET_PATH
  7596.         SetErrorMode(SEM_FAILCRITICALERRORS);  // So that a floppy drive doesn't prompt for a disk
  7597.         return g_ErrorLevel->Assign(SetVolumeLabel(path, aValue2) ? ERRORLEVEL_NONE : ERRORLEVEL_ERROR);
  7598.  
  7599.     } // switch()
  7600.  
  7601.     return FAIL;  // Should never be executed.  Helps catch bugs.
  7602. }
  7603.  
  7604.  
  7605.  
  7606. ResultType Line::DriveLock(char aDriveLetter, bool aLockIt)
  7607. {
  7608.     HANDLE hdevice;
  7609.     DWORD unused;
  7610.     BOOL result;
  7611.  
  7612.     if (g_os.IsWin9x())
  7613.     {
  7614.         // blisteringhot@hotmail.com has confirmed that the code below works on Win98 with an IDE CD Drive:
  7615.         // System:  Win98 IDE CdRom (my ejecter is CloseTray)
  7616.         // I get a blue screen when I try to eject after using the test script.
  7617.         // "eject request to drive in use"
  7618.         // It asks me to Ok or Esc, Ok is default.
  7619.         //    -probably a bit scary for a novice.
  7620.         // BUT its locked alright!"
  7621.  
  7622.         // Use the Windows 9x method.  The code below is based on an example posted by Microsoft.
  7623.         // Note: The presence of the code below does not add a detectible amount to the EXE size
  7624.         // (probably because it's mostly defines and data types).
  7625.         #pragma pack(1)
  7626.         typedef struct _DIOC_REGISTERS
  7627.         {
  7628.             DWORD reg_EBX;
  7629.             DWORD reg_EDX;
  7630.             DWORD reg_ECX;
  7631.             DWORD reg_EAX;
  7632.             DWORD reg_EDI;
  7633.             DWORD reg_ESI;
  7634.             DWORD reg_Flags;
  7635.         } DIOC_REGISTERS, *PDIOC_REGISTERS;
  7636.         typedef struct _PARAMBLOCK
  7637.         {
  7638.             BYTE Operation;
  7639.             BYTE NumLocks;
  7640.         } PARAMBLOCK, *PPARAMBLOCK;
  7641.         #pragma pack()
  7642.  
  7643.         // MS: Prepare for lock or unlock IOCTL call
  7644.         #define CARRY_FLAG 0x1
  7645.         #define VWIN32_DIOC_DOS_IOCTL 1
  7646.         #define LOCK_MEDIA   0
  7647.         #define UNLOCK_MEDIA 1
  7648.         #define STATUS_LOCK  2
  7649.         PARAMBLOCK pb = {0};
  7650.         pb.Operation = aLockIt ? LOCK_MEDIA : UNLOCK_MEDIA;
  7651.         
  7652.         DIOC_REGISTERS regs = {0};
  7653.         regs.reg_EAX = 0x440D;
  7654.         regs.reg_EBX = toupper(aDriveLetter) - 'A' + 1; // Convert to drive index. 0 = default, 1 = A, 2 = B, 3 = C
  7655.         regs.reg_ECX = 0x0848; // MS: Lock/unlock media
  7656.         regs.reg_EDX = (DWORD)(size_t)&pb;
  7657.         
  7658.         // MS: Open VWIN32
  7659.         hdevice = CreateFile("\\\\.\\vwin32", 0, 0, NULL, 0, FILE_FLAG_DELETE_ON_CLOSE, NULL);
  7660.         if (hdevice == INVALID_HANDLE_VALUE)
  7661.             return FAIL;
  7662.         
  7663.         // MS: Call VWIN32
  7664.         result = DeviceIoControl(hdevice, VWIN32_DIOC_DOS_IOCTL, ®s, sizeof(regs), ®s, sizeof(regs), &unused, 0);
  7665.         if (result)
  7666.             result = !(regs.reg_Flags & CARRY_FLAG);
  7667.     }
  7668.     else // NT4/2k/XP or later
  7669.     {
  7670.         // The calls below cannot work on Win9x (as documented by MSDN's PREVENT_MEDIA_REMOVAL).
  7671.         // Don't even attempt them on Win9x because they might blow up.
  7672.         char filename[64];
  7673.         sprintf(filename, "\\\\.\\%c:", aDriveLetter);
  7674.         // FILE_READ_ATTRIBUTES is not enough; it yields "Access Denied" error.  So apparently all or part
  7675.         // of the sub-attributes in GENERIC_READ are needed.  An MSDN example implies that GENERIC_WRITE is
  7676.         // only needed for GetDriveType() == DRIVE_REMOVABLE drives, and maybe not even those when all we
  7677.         // want to do is lock/unlock the drive (that example did quite a bit more).  In any case, research
  7678.         // indicates that all CD/DVD drives are ever considered DRIVE_CDROM, not DRIVE_REMOVABLE.
  7679.         // Due to this and the unlikelihood that GENERIC_WRITE is ever needed anyway, GetDriveType() is
  7680.         // not called for the purpose of conditionally adding the GENERIC_WRITE attribute.
  7681.         hdevice = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
  7682.         if (hdevice == INVALID_HANDLE_VALUE)
  7683.             return FAIL;
  7684.         PREVENT_MEDIA_REMOVAL pmr;
  7685.         pmr.PreventMediaRemoval = aLockIt;
  7686.         result = DeviceIoControl(hdevice, IOCTL_STORAGE_MEDIA_REMOVAL, &pmr, sizeof(PREVENT_MEDIA_REMOVAL)
  7687.             , NULL, 0, &unused, NULL);
  7688.     }
  7689.     CloseHandle(hdevice);
  7690.     return result ? OK : FAIL;
  7691. }
  7692.  
  7693.  
  7694.  
  7695. ResultType Line::DriveGet(char *aCmd, char *aValue)
  7696. {
  7697.     DriveGetCmds drive_get_cmd = ConvertDriveGetCmd(aCmd);
  7698.     if (drive_get_cmd == DRIVEGET_CMD_CAPACITY)
  7699.         return DriveSpace(aValue, false);
  7700.  
  7701.     char path[MAX_PATH + 1];  // +1 to allow room for trailing backslash in case it needs to be added.
  7702.     size_t path_length;
  7703.  
  7704.     if (drive_get_cmd == DRIVEGET_CMD_SETLABEL) // The is retained for backward compatibility even though the Drive cmd is normally used.
  7705.     {
  7706.         DRIVE_SET_PATH
  7707.         SetErrorMode(SEM_FAILCRITICALERRORS); // If drive is a floppy, prevents pop-up dialog prompting to insert disk.
  7708.         char *new_label = omit_leading_whitespace(aCmd + 9);  // Example: SetLabel:MyLabel
  7709.         return g_ErrorLevel->Assign(SetVolumeLabel(path, new_label) ? ERRORLEVEL_NONE : ERRORLEVEL_ERROR);
  7710.     }
  7711.  
  7712.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR);  // Set default since there are many points of return.
  7713.  
  7714.     Var &output_var = *OUTPUT_VAR;
  7715.  
  7716.     switch(drive_get_cmd)
  7717.     {
  7718.  
  7719.     case DRIVEGET_CMD_INVALID:
  7720.         // Since command names are validated at load-time, this only happens if the command name
  7721.         // was contained in a variable reference.  Since that is very rare, just set ErrorLevel
  7722.         // and return:
  7723.         return output_var.Assign();  // Let ErrorLevel tell the story.
  7724.  
  7725.     case DRIVEGET_CMD_LIST:
  7726.     {
  7727.         UINT drive_type;
  7728.         #define ALL_DRIVE_TYPES 256
  7729.         if (!*aValue) drive_type = ALL_DRIVE_TYPES;
  7730.         else if (!stricmp(aValue, "CDRom")) drive_type = DRIVE_CDROM;
  7731.         else if (!stricmp(aValue, "Removable")) drive_type = DRIVE_REMOVABLE;
  7732.         else if (!stricmp(aValue, "Fixed")) drive_type = DRIVE_FIXED;
  7733.         else if (!stricmp(aValue, "Network")) drive_type = DRIVE_REMOTE;
  7734.         else if (!stricmp(aValue, "Ramdisk")) drive_type = DRIVE_RAMDISK;
  7735.         else if (!stricmp(aValue, "Unknown")) drive_type = DRIVE_UNKNOWN;
  7736.         else // Let ErrorLevel tell the story.
  7737.             return OK;
  7738.  
  7739.         char found_drives[32];  // Need room for all 26 possible drive letters.
  7740.         int found_drives_count;
  7741.         UCHAR letter;
  7742.         char buf[128], *buf_ptr;
  7743.  
  7744.         SetErrorMode(SEM_FAILCRITICALERRORS); // If drive is a floppy, prevents pop-up dialog prompting to insert disk.
  7745.  
  7746.         for (found_drives_count = 0, letter = 'A'; letter <= 'Z'; ++letter)
  7747.         {
  7748.             buf_ptr = buf;
  7749.             *buf_ptr++ = letter;
  7750.             *buf_ptr++ = ':';
  7751.             *buf_ptr++ = '\\';
  7752.             *buf_ptr = '\0';
  7753.             UINT this_type = GetDriveType(buf);
  7754.             if (this_type == drive_type || (drive_type == ALL_DRIVE_TYPES && this_type != DRIVE_NO_ROOT_DIR))
  7755.                 found_drives[found_drives_count++] = letter;  // Store just the drive letters.
  7756.         }
  7757.         found_drives[found_drives_count] = '\0';  // Terminate the string of found drive letters.
  7758.         output_var.Assign(found_drives);
  7759.         if (!*found_drives)
  7760.             return OK;  // Seems best to flag zero drives in the system as default ErrorLevel of "1".
  7761.         break;
  7762.     }
  7763.  
  7764.     case DRIVEGET_CMD_FILESYSTEM:
  7765.     case DRIVEGET_CMD_LABEL:
  7766.     case DRIVEGET_CMD_SERIAL:
  7767.     {
  7768.         char volume_name[256];
  7769.         char file_system[256];
  7770.         DRIVE_SET_PATH
  7771.         SetErrorMode(SEM_FAILCRITICALERRORS); // If drive is a floppy, prevents pop-up dialog prompting to insert disk.
  7772.         DWORD serial_number, max_component_length, file_system_flags;
  7773.         if (!GetVolumeInformation(path, volume_name, sizeof(volume_name) - 1, &serial_number, &max_component_length
  7774.             , &file_system_flags, file_system, sizeof(file_system) - 1))
  7775.             return output_var.Assign(); // Let ErrorLevel tell the story.
  7776.         switch(drive_get_cmd)
  7777.         {
  7778.         case DRIVEGET_CMD_FILESYSTEM: output_var.Assign(file_system); break;
  7779.         case DRIVEGET_CMD_LABEL: output_var.Assign(volume_name); break;
  7780.         case DRIVEGET_CMD_SERIAL: output_var.Assign(serial_number); break;
  7781.         }
  7782.         break;
  7783.     }
  7784.  
  7785.     case DRIVEGET_CMD_TYPE:
  7786.     {
  7787.         DRIVE_SET_PATH
  7788.         SetErrorMode(SEM_FAILCRITICALERRORS); // If drive is a floppy, prevents pop-up dialog prompting to insert disk.
  7789.         switch (GetDriveType(path))
  7790.         {
  7791.         case DRIVE_UNKNOWN:   output_var.Assign("Unknown"); break;
  7792.         case DRIVE_REMOVABLE: output_var.Assign("Removable"); break;
  7793.         case DRIVE_FIXED:     output_var.Assign("Fixed"); break;
  7794.         case DRIVE_REMOTE:    output_var.Assign("Network"); break;
  7795.         case DRIVE_CDROM:     output_var.Assign("CDROM"); break;
  7796.         case DRIVE_RAMDISK:   output_var.Assign("RAMDisk"); break;
  7797.         default: // DRIVE_NO_ROOT_DIR
  7798.             return output_var.Assign();  // Let ErrorLevel tell the story.
  7799.         }
  7800.         break;
  7801.     }
  7802.  
  7803.     case DRIVEGET_CMD_STATUS:
  7804.     {
  7805.         DRIVE_SET_PATH
  7806.         SetErrorMode(SEM_FAILCRITICALERRORS); // If drive is a floppy, prevents pop-up dialog prompting to insert disk.
  7807.         DWORD sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters;
  7808.         switch (GetDiskFreeSpace(path, §ors_per_cluster, &bytes_per_sector, &free_clusters, &total_clusters)
  7809.             ? ERROR_SUCCESS : GetLastError())
  7810.         {
  7811.         case ERROR_SUCCESS:        output_var.Assign("Ready"); break;
  7812.         case ERROR_PATH_NOT_FOUND: output_var.Assign("Invalid"); break;
  7813.         case ERROR_NOT_READY:      output_var.Assign("NotReady"); break;
  7814.         case ERROR_WRITE_PROTECT:  output_var.Assign("ReadOnly"); break;
  7815.         default:                   output_var.Assign("Unknown");
  7816.         }
  7817.         break;
  7818.     }
  7819.  
  7820.     case DRIVEGET_CMD_STATUSCD:
  7821.         // Don't do DRIVE_SET_PATH in this case since trailing backslash might prevent it from
  7822.         // working on some OSes.
  7823.         // It seems best not to do the below check since:
  7824.         // 1) aValue usually lacks a trailing backslash so that it will work correctly with "open c: type cdaudio".
  7825.         //    That lack might prevent DriveGetType() from working on some OSes.
  7826.         // 2) It's conceivable that tray eject/retract might work on certain types of drives even though
  7827.         //    they aren't of type DRIVE_CDROM.
  7828.         // 3) One or both of the calls to mciSendString() will simply fail if the drive isn't of the right type.
  7829.         //if (GetDriveType(aValue) != DRIVE_CDROM) // Testing reveals that the below method does not work on Network CD/DVD drives.
  7830.         //    return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  7831.         char mci_string[256], status[128];
  7832.         // Note that there is apparently no way to determine via mciSendString() whether the tray is ejected
  7833.         // or not, since "open" is returned even when the tray is closed but there is no media.
  7834.         if (!*aValue) // When drive is omitted, operate upon default CD/DVD drive.
  7835.         {
  7836.             if (mciSendString("status cdaudio mode", status, sizeof(status), NULL)) // Error.
  7837.                 return output_var.Assign(); // Let ErrorLevel tell the story.
  7838.         }
  7839.         else // Operate upon a specific drive letter.
  7840.         {
  7841.             snprintf(mci_string, sizeof(mci_string), "open %s type cdaudio alias cd wait shareable", aValue);
  7842.             if (mciSendString(mci_string, NULL, 0, NULL)) // Error.
  7843.                 return output_var.Assign(); // Let ErrorLevel tell the story.
  7844.             MCIERROR error = mciSendString("status cd mode", status, sizeof(status), NULL);
  7845.             mciSendString("close cd wait", NULL, 0, NULL);
  7846.             if (error)
  7847.                 return output_var.Assign(); // Let ErrorLevel tell the story.
  7848.         }
  7849.         // Otherwise, success:
  7850.         output_var.Assign(status);
  7851.         break;
  7852.  
  7853.     } // switch()
  7854.  
  7855.     // Note that ControlDelay is not done for the Get type commands, because it seems unnecessary.
  7856.     return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  7857. }
  7858.  
  7859.  
  7860.  
  7861. ResultType Line::SoundSetGet(char *aSetting, DWORD aComponentType, int aComponentInstance
  7862.     , DWORD aControlType, UINT aMixerID)
  7863. // If the caller specifies NULL for aSetting, the mode will be "Get".  Otherwise, it will be "Set".
  7864. {
  7865.     #define SOUND_MODE_IS_SET aSetting // Boolean: i.e. if it's not NULL, the mode is "SET".
  7866.     double setting_percent;
  7867.     Var *output_var;
  7868.     if (SOUND_MODE_IS_SET)
  7869.     {
  7870.         output_var = NULL; // To help catch bugs.
  7871.         setting_percent = ATOF(aSetting);
  7872.         if (setting_percent < -100)
  7873.             setting_percent = -100;
  7874.         else if (setting_percent > 100)
  7875.             setting_percent = 100;
  7876.     }
  7877.     else // The mode is GET.
  7878.     {
  7879.         output_var = OUTPUT_VAR;
  7880.         output_var->Assign(); // Init to empty string regardless of whether we succeed here.
  7881.     }
  7882.  
  7883.     // Rare, since load-time validation would have caught problems unless the params were variable references.
  7884.     // Text values for ErrorLevels should be kept below 64 characters in length so that the variable doesn't
  7885.     // have to be expanded with a different memory allocation method:
  7886.     if (aControlType == MIXERCONTROL_CONTROLTYPE_INVALID || aComponentType == MIXERLINE_COMPONENTTYPE_DST_UNDEFINED)
  7887.         return g_ErrorLevel->Assign("Invalid Control Type or Component Type");
  7888.     
  7889.     // Open the specified mixer ID:
  7890.     HMIXER hMixer;
  7891.     if (mixerOpen(&hMixer, aMixerID, 0, 0, 0) != MMSYSERR_NOERROR)
  7892.         return g_ErrorLevel->Assign("Can't Open Specified Mixer");
  7893.  
  7894.     // Find out how many destinations are available on this mixer (should always be at least one):
  7895.     int dest_count;
  7896.     MIXERCAPS mxcaps;
  7897.     if (mixerGetDevCaps((UINT_PTR)hMixer, &mxcaps, sizeof(mxcaps)) == MMSYSERR_NOERROR)
  7898.         dest_count = mxcaps.cDestinations;
  7899.     else
  7900.         dest_count = 1;  // Assume it has one so that we can try to proceed anyway.
  7901.  
  7902.     // Find specified line (aComponentType + aComponentInstance):
  7903.     MIXERLINE ml = {0};
  7904.     ml.cbStruct = sizeof(ml);
  7905.     if (aComponentInstance == 1)  // Just get the first line of this type, the easy way.
  7906.     {
  7907.         ml.dwComponentType = aComponentType;
  7908.         if (mixerGetLineInfo((HMIXEROBJ)hMixer, &ml, MIXER_GETLINEINFOF_COMPONENTTYPE) != MMSYSERR_NOERROR)
  7909.         {
  7910.             mixerClose(hMixer);
  7911.             return g_ErrorLevel->Assign("Mixer Doesn't Support This Component Type");
  7912.         }
  7913.     }
  7914.     else
  7915.     {
  7916.         // Search through each source of each destination, looking for the indicated instance
  7917.         // number for the indicated component type:
  7918.         int source_count;
  7919.         bool found = false;
  7920.         for (int d = 0, found_instance = 0; d < dest_count && !found; ++d) // For each destination of this mixer.
  7921.         {
  7922.             ml.dwDestination = d;
  7923.             if (mixerGetLineInfo((HMIXEROBJ)hMixer, &ml, MIXER_GETLINEINFOF_DESTINATION) != MMSYSERR_NOERROR)
  7924.                 // Keep trying in case the others can be retrieved.
  7925.                 continue;
  7926.             source_count = ml.cConnections;  // Make a copy of this value so that the struct can be reused.
  7927.             for (int s = 0; s < source_count && !found; ++s) // For each source of this destination.
  7928.             {
  7929.                 ml.dwDestination = d; // Set it again in case it was changed.
  7930.                 ml.dwSource = s;
  7931.                 if (mixerGetLineInfo((HMIXEROBJ)hMixer, &ml, MIXER_GETLINEINFOF_SOURCE) != MMSYSERR_NOERROR)
  7932.                     // Keep trying in case the others can be retrieved.
  7933.                     continue;
  7934.                 // This line can be used to show a soundcard's component types (match them against mmsystem.h):
  7935.                 //MsgBox(ml.dwComponentType);
  7936.                 if (ml.dwComponentType == aComponentType)
  7937.                 {
  7938.                     ++found_instance;
  7939.                     if (found_instance == aComponentInstance)
  7940.                         found = true;
  7941.                 }
  7942.             } // inner for()
  7943.         } // outer for()
  7944.         if (!found)
  7945.         {
  7946.             mixerClose(hMixer);
  7947.             return g_ErrorLevel->Assign("Mixer Doesn't Have That Many of That Component Type");
  7948.         }
  7949.     }
  7950.  
  7951.     // Find the mixer control (aControlType) for the above component:
  7952.     MIXERCONTROL mc; // MSDN: "No initialization of the buffer pointed to by [pamxctrl below] is required"
  7953.     MIXERLINECONTROLS mlc;
  7954.     mlc.cbStruct = sizeof(mlc);
  7955.     mlc.pamxctrl = &mc;
  7956.     mlc.cbmxctrl = sizeof(mc);
  7957.     mlc.dwLineID = ml.dwLineID;
  7958.     mlc.dwControlType = aControlType;
  7959.     mlc.cControls = 1;
  7960.     if (mixerGetLineControls((HMIXEROBJ)hMixer, &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE) != MMSYSERR_NOERROR)
  7961.     {
  7962.         mixerClose(hMixer);
  7963.         return g_ErrorLevel->Assign("Component Doesn't Support This Control Type");
  7964.     }
  7965.  
  7966.     // Does user want to adjust the current setting by a certain amount?
  7967.     // For v1.0.25, the first char of RAW_ARG is also checked in case this is an expression intended
  7968.     // to be a positive offset, such as +(var + 10)
  7969.     bool adjust_current_setting = aSetting && (*aSetting == '-' || *aSetting == '+' || *RAW_ARG1 == '+');
  7970.  
  7971.     // These are used in more than once place, so always initialize them here:
  7972.     MIXERCONTROLDETAILS mcd = {0};
  7973.     MIXERCONTROLDETAILS_UNSIGNED mcdMeter;
  7974.     mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
  7975.     mcd.dwControlID = mc.dwControlID;
  7976.     mcd.cChannels = 1; // MSDN: "when an application needs to get and set all channels as if they were uniform"
  7977.     mcd.paDetails = &mcdMeter;
  7978.     mcd.cbDetails = sizeof(mcdMeter);
  7979.  
  7980.     // Get the current setting of the control, if necessary:
  7981.     if (!SOUND_MODE_IS_SET || adjust_current_setting)
  7982.     {
  7983.         if (mixerGetControlDetails((HMIXEROBJ)hMixer, &mcd, MIXER_GETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR)
  7984.         {
  7985.             mixerClose(hMixer);
  7986.             return g_ErrorLevel->Assign("Can't Get Current Setting");
  7987.         }
  7988.     }
  7989.  
  7990.     bool control_type_is_boolean;
  7991.     switch (aControlType)
  7992.     {
  7993.     case MIXERCONTROL_CONTROLTYPE_ONOFF:
  7994.     case MIXERCONTROL_CONTROLTYPE_MUTE:
  7995.     case MIXERCONTROL_CONTROLTYPE_MONO:
  7996.     case MIXERCONTROL_CONTROLTYPE_LOUDNESS:
  7997.     case MIXERCONTROL_CONTROLTYPE_STEREOENH:
  7998.     case MIXERCONTROL_CONTROLTYPE_BASS_BOOST:
  7999.         control_type_is_boolean = true;
  8000.         break;
  8001.     default: // For all others, assume the control can have more than just ON/OFF as its allowed states.
  8002.         control_type_is_boolean = false;
  8003.     }
  8004.  
  8005.     if (SOUND_MODE_IS_SET)
  8006.     {
  8007.         if (control_type_is_boolean)
  8008.         {
  8009.             if (adjust_current_setting) // The user wants this toggleable control to be toggled to its opposite state:
  8010.                 mcdMeter.dwValue = (mcdMeter.dwValue > mc.Bounds.dwMinimum) ? mc.Bounds.dwMinimum : mc.Bounds.dwMaximum;
  8011.             else // Set the value according to whether the user gave us a setting that is greater than zero:
  8012.                 mcdMeter.dwValue = (setting_percent > 0.0) ? mc.Bounds.dwMaximum : mc.Bounds.dwMinimum;
  8013.         }
  8014.         else // For all others, assume the control can have more than just ON/OFF as its allowed states.
  8015.         {
  8016.             // Make this an __int64 vs. DWORD to avoid underflow (so that a setting_percent of -100
  8017.             // is supported whenenver the difference between Min and Max is large, such as MAXDWORD):
  8018.             __int64 specified_vol = (__int64)((mc.Bounds.dwMaximum - mc.Bounds.dwMinimum) * (setting_percent / 100.0));
  8019.             if (adjust_current_setting)
  8020.             {
  8021.                 // Make it a big int so that overflow/underflow can be detected:
  8022.                 __int64 vol_new = mcdMeter.dwValue + specified_vol;
  8023.                 if (vol_new < mc.Bounds.dwMinimum)
  8024.                     vol_new = mc.Bounds.dwMinimum;
  8025.                 else if (vol_new > mc.Bounds.dwMaximum)
  8026.                     vol_new = mc.Bounds.dwMaximum;
  8027.                 mcdMeter.dwValue = (DWORD)vol_new;
  8028.             }
  8029.             else
  8030.                 mcdMeter.dwValue = (DWORD)specified_vol; // Due to the above, it's known to be positive in this case.
  8031.         }
  8032.  
  8033.         MMRESULT result = mixerSetControlDetails((HMIXEROBJ)hMixer, &mcd, MIXER_GETCONTROLDETAILSF_VALUE);
  8034.         mixerClose(hMixer);
  8035.         return g_ErrorLevel->Assign(result == MMSYSERR_NOERROR ? ERRORLEVEL_NONE : "Can't Change Setting");
  8036.     }
  8037.  
  8038.     // Otherwise, the mode is "Get":
  8039.     mixerClose(hMixer);
  8040.     g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  8041.  
  8042.     if (control_type_is_boolean)
  8043.         return output_var->Assign(mcdMeter.dwValue ? "On" : "Off");
  8044.     else // For all others, assume the control can have more than just ON/OFF as its allowed states.
  8045.         // The MSDN docs imply that values fetched via the above method do not distinguish between
  8046.         // left and right volume levels, unlike waveOutGetVolume():
  8047.         return output_var->Assign(   ((double)100 * (mcdMeter.dwValue - (DWORD)mc.Bounds.dwMinimum))
  8048.             / (mc.Bounds.dwMaximum - mc.Bounds.dwMinimum)   );
  8049. }
  8050.  
  8051.  
  8052.  
  8053. ResultType Line::SoundGetWaveVolume(HWAVEOUT aDeviceID)
  8054. {
  8055.     OUTPUT_VAR->Assign(); // Init to empty string regardless of whether we succeed here.
  8056.  
  8057.     DWORD current_vol;
  8058.     if (waveOutGetVolume(aDeviceID, ¤t_vol) != MMSYSERR_NOERROR)
  8059.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  8060.  
  8061.     g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  8062.  
  8063.     // Return only the left channel volume level (in case right is different, or device is mono vs. stereo).
  8064.     // MSDN: "If a device does not support both left and right volume control, the low-order word
  8065.     // of the specified location contains the mono volume level.
  8066.     return OUTPUT_VAR->Assign((double)(LOWORD(current_vol) * 100) / 0xFFFF);
  8067. }
  8068.  
  8069.  
  8070.  
  8071. ResultType Line::SoundSetWaveVolume(char *aVolume, HWAVEOUT aDeviceID)
  8072. {
  8073.     double volume = ATOF(aVolume);
  8074.     if (volume < -100)
  8075.         volume = -100;
  8076.     else if (volume > 100)
  8077.         volume = 100;
  8078.  
  8079.     // Make this an int vs. WORD so that negative values are supported (e.g. adjust volume by -10%).
  8080.     int specified_vol_per_channel = (int)(0xFFFF * (volume / 100));
  8081.     DWORD vol_new;
  8082.  
  8083.     // For v1.0.25, the first char of RAW_ARG is also checked in case this is an expression intended
  8084.     // to be a positive offset, such as +(var + 10)
  8085.     if (*aVolume == '-' || *aVolume == '+' || *RAW_ARG1 == '+') // User wants to adjust the current level by a certain amount.
  8086.     {
  8087.         DWORD current_vol;
  8088.         if (waveOutGetVolume(aDeviceID, ¤t_vol) != MMSYSERR_NOERROR)
  8089.             return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  8090.         // Adjust left & right independently so that we at least attempt to retain the user's
  8091.         // balance setting (if overflow or underflow occurs, the relative balance might be lost):
  8092.         int vol_left = LOWORD(current_vol); // Make them ints so that overflow/underflow can be detected.
  8093.         int vol_right = HIWORD(current_vol);
  8094.         vol_left += specified_vol_per_channel;
  8095.         vol_right += specified_vol_per_channel;
  8096.         // Handle underflow or overflow:
  8097.         if (vol_left < 0)
  8098.             vol_left = 0;
  8099.         else if (vol_left > 0xFFFF)
  8100.             vol_left = 0xFFFF;
  8101.         if (vol_right < 0)
  8102.             vol_right = 0;
  8103.         else if (vol_right > 0xFFFF)
  8104.             vol_right = 0xFFFF;
  8105.         vol_new = MAKELONG((WORD)vol_left, (WORD)vol_right);  // Left is low-order, right is high-order.
  8106.     }
  8107.     else // User wants the volume level set to an absolute level (i.e. ignore its current level).
  8108.         vol_new = MAKELONG((WORD)specified_vol_per_channel, (WORD)specified_vol_per_channel);
  8109.  
  8110.     if (waveOutSetVolume(aDeviceID, vol_new) == MMSYSERR_NOERROR)
  8111.         return g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  8112.     else
  8113.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  8114. }
  8115.  
  8116.  
  8117.  
  8118. ResultType Line::SoundPlay(char *aFilespec, bool aSleepUntilDone)
  8119. {
  8120.     char *cp = omit_leading_whitespace(aFilespec);
  8121.     if (*cp == '*')
  8122.         return g_ErrorLevel->Assign(MessageBeep(ATOU(cp + 1)) ? ERRORLEVEL_NONE : ERRORLEVEL_ERROR);
  8123.         // ATOU() returns 0xFFFFFFFF for -1, which is relied upon to support the -1 sound.
  8124.     // See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_play.asp
  8125.     // for some documentation mciSendString() and related.
  8126.     char buf[MAX_PATH * 2]; // Allow room for filename and commands.
  8127.     mciSendString("status " SOUNDPLAY_ALIAS " mode", buf, sizeof(buf), NULL);
  8128.     if (*buf) // "playing" or "stopped" (so close it before trying to re-open with a new aFilespec).
  8129.         mciSendString("close " SOUNDPLAY_ALIAS, NULL, 0, NULL);
  8130.     snprintf(buf, sizeof(buf), "open \"%s\" alias " SOUNDPLAY_ALIAS, aFilespec);
  8131.     if (mciSendString(buf, NULL, 0, NULL)) // Failure.
  8132.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);  // Let ErrorLevel tell the story.
  8133.     g_SoundWasPlayed = true;  // For use by Script's destructor.
  8134.     if (mciSendString("play " SOUNDPLAY_ALIAS, NULL, 0, NULL)) // Failure.
  8135.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);  // Let ErrorLevel tell the story.
  8136.     // Otherwise, the sound is now playing.
  8137.     g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  8138.     if (!aSleepUntilDone)
  8139.         return OK;
  8140.     // Otherwise, caller wants us to wait until the file is done playing.  To allow our app to remain
  8141.     // responsive during this time, use a loop that checks our message queue:
  8142.     // Older method: "mciSendString("play " SOUNDPLAY_ALIAS " wait", NULL, 0, NULL)"
  8143.     for (;;)
  8144.     {
  8145.         mciSendString("status " SOUNDPLAY_ALIAS " mode", buf, sizeof(buf), NULL);
  8146.         if (!*buf) // Probably can't happen given the state we're in.
  8147.             break;
  8148.         if (!strcmp(buf, "stopped")) // The sound is done playing.
  8149.         {
  8150.             mciSendString("close " SOUNDPLAY_ALIAS, NULL, 0, NULL);
  8151.             break;
  8152.         }
  8153.         // Sleep a little longer than normal because I'm not sure how much overhead
  8154.         // and CPU utilization the above incurs:
  8155.         MsgSleep(20);
  8156.     }
  8157.     return OK;
  8158. }
  8159.  
  8160.  
  8161.  
  8162. void SetWorkingDir(char *aNewDir)
  8163. // Sets ErrorLevel to indicate success/failure, but only if the script has begun runtime execution (callers
  8164. // want that).
  8165. // This function was added in v1.0.45.01 for the reasons commented further below.
  8166. // It's similar to the one in the ahk2exe source, so maintain them together.
  8167. {
  8168.     if (!SetCurrentDirectory(aNewDir)) // Caused by nonexistent directory, permission denied, etc.
  8169.     {
  8170.         if (g_script.mIsReadyToExecute)
  8171.             g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  8172.         return;
  8173.     }
  8174.  
  8175.     // Otherwise, the change to the working directory *apparently* succeeded (but is confirmed below for root drives
  8176.     // and also because we want the absolute path in cases where aNewDir is relative).
  8177.     char buf[sizeof(g_WorkingDir)];
  8178.     char *actual_working_dir = g_script.mIsReadyToExecute ? g_WorkingDir : buf; // i.e. don't update g_WorkingDir when our caller is the #include directive.
  8179.     // Other than during program startup, this should be the only place where the official
  8180.     // working dir can change.  The exception is FileSelectFile(), which changes the working
  8181.     // dir as the user navigates from folder to folder.  However, the whole purpose of
  8182.     // maintaining g_WorkingDir is to workaround that very issue.
  8183.  
  8184.     // GetCurrentDirectory() is called explicitly, to confirm the change, in case aNewDir is a relative path.
  8185.     // We want to store the absolute path:
  8186.     if (!GetCurrentDirectory(sizeof(buf), actual_working_dir)) // Might never fail in this case, but kept for backward compatibility.
  8187.     {
  8188.         strlcpy(actual_working_dir, aNewDir, sizeof(buf)); // Update the global to the best info available.
  8189.         // But ErrorLevel is set to "none" further below because the actual "set" did succeed; it's also for
  8190.         // backward compatibility.
  8191.     }
  8192.     else // GetCurrentDirectory() succeeded, so it's appropriate to compare what we asked for to what was received.
  8193.     {
  8194.         if (aNewDir[0] && aNewDir[1] == ':' && !aNewDir[2] // Root with missing backslash. Relies on short-circuit boolean order.
  8195.             && stricmp(aNewDir, actual_working_dir)) // The root directory we requested didn't actually get set. See below.
  8196.         {
  8197.             // There is some strange OS behavior here: If the current working directory is C:\anything\...
  8198.             // and SetCurrentDirectory() is called to switch to "C:", the function reports success but doesn't
  8199.             // actually change the directory.  However, if you first change to D: then back to C:, the change
  8200.             // works as expected.  Presumably this is for backward compatibility with DOS days; but it's 
  8201.             // inconvenient and seems desirable to override it in this case, especially because:
  8202.             // v1.0.45.01: Since A_ScriptDir omits the trailing backslash for roots of drives (such as C:),
  8203.             // and since that variable probably shouldn't be changed for backward compatibility, provide
  8204.             // the missing backslash to allow SetWorkingDir %A_ScriptDir% (and others) to work in the root
  8205.             // of a drive.
  8206.             char buf_temp[8];
  8207.             sprintf(buf_temp, "%s\\", aNewDir); // No danger of buffer overflow in this case.
  8208.             if (SetCurrentDirectory(buf_temp))
  8209.             {
  8210.                 if (!GetCurrentDirectory(sizeof(buf), actual_working_dir)) // Might never fail in this case, but kept for backward compatibility.
  8211.                     strlcpy(actual_working_dir, aNewDir, sizeof(buf)); // But still report "no error" (below) because the Set() actually did succeed.
  8212.                     // But treat this as a success like the similar one higher above.
  8213.             }
  8214.             //else Set() failed; but since the origial Set() succeeded (and for simplicity) report ErrorLevel "none".
  8215.         }
  8216.     }
  8217.  
  8218.     // Since the above didn't return, it wants us to indicate success.
  8219.     if (g_script.mIsReadyToExecute) // Callers want ErrorLevel changed only during script runtime.
  8220.         g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  8221. }
  8222.  
  8223.  
  8224.  
  8225. ResultType Line::FileSelectFile(char *aOptions, char *aWorkingDir, char *aGreeting, char *aFilter)
  8226. // Since other script threads can interrupt this command while it's running, it's important that
  8227. // this command not refer to sArgDeref[] and sArgVar[] anytime after an interruption becomes possible.
  8228. // This is because an interrupting thread usually changes the values to something inappropriate for this thread.
  8229. {
  8230.     Var &output_var = *OUTPUT_VAR; // Fix for v1.0.45.01: Must be resolved and saved early.  See comment above.
  8231.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  8232.     if (g_nFileDialogs >= MAX_FILEDIALOGS)
  8233.     {
  8234.         // Have a maximum to help prevent runaway hotkeys due to key-repeat feature, etc.
  8235.         MsgBox("The maximum number of File Dialogs has been reached." ERR_ABORT);
  8236.         return FAIL;
  8237.     }
  8238.     
  8239.     // Large in case more than one file is allowed to be selected.
  8240.     // The call to GetOpenFileName() may fail if the first character of the buffer isn't NULL
  8241.     // because it then thinks the buffer contains the default filename, which if it's uninitialized
  8242.     // may be a string that's too long.
  8243.     char file_buf[65535] = ""; // Set default.
  8244.  
  8245.     char working_dir[MAX_PATH];
  8246.     if (!aWorkingDir || !*aWorkingDir)
  8247.         *working_dir = '\0';
  8248.     else
  8249.     {
  8250.         strlcpy(working_dir, aWorkingDir, sizeof(working_dir));
  8251.         // v1.0.43.10: Support CLSIDs such as:
  8252.         //   My Computer  ::{20d04fe0-3aea-1069-a2d8-08002b30309d}
  8253.         //   My Documents ::{450d8fba-ad25-11d0-98a8-0800361b1103}
  8254.         // Also support optional subdirectory appended to the CLSID.
  8255.         // Neither SetCurrentDirectory() nor GetFileAttributes() directly supports CLSIDs, so rely on other means
  8256.         // to detect whether a CLSID ends in a directory vs. filename.
  8257.         bool is_directory, is_clsid;
  8258.         if (is_clsid = !strncmp(working_dir, "::{", 3))
  8259.         {
  8260.             char *end_brace;
  8261.             if (end_brace = strchr(working_dir, '}'))
  8262.                 is_directory = !end_brace[1] // First '}' is also the last char in string, so it's naked CLSID (so assume directory).
  8263.                     || working_dir[strlen(working_dir) - 1] == '\\'; // Or path ends in backslash.
  8264.             else // Badly formatted clsid.
  8265.                 is_directory = true; // Arbitrary default due to rarity.
  8266.         }
  8267.         else // Not a CLSID.
  8268.         {
  8269.             DWORD attr = GetFileAttributes(working_dir);
  8270.             is_directory = (attr != 0xFFFFFFFF) && (attr & FILE_ATTRIBUTE_DIRECTORY);
  8271.         }
  8272.         if (!is_directory)
  8273.         {
  8274.             // Above condition indicates it's either an existing file that's not a folder, or a nonexistent
  8275.             // folder/filename.  In either case, it seems best to assume it's a file because the user may want
  8276.             // to provide a default SAVE filename, and it would be normal for such a file not to already exist.
  8277.             char *last_backslash;
  8278.             if (last_backslash = strrchr(working_dir, '\\'))
  8279.             {
  8280.                 strlcpy(file_buf, last_backslash + 1, sizeof(file_buf)); // Set the default filename.
  8281.                 *last_backslash = '\0'; // Make the working directory just the file's path.
  8282.             }
  8283.             else // The entire working_dir string is the default file (unless this is a clsid).
  8284.                 if (!is_clsid)
  8285.                 {
  8286.                     strlcpy(file_buf, working_dir, sizeof(file_buf));
  8287.                     *working_dir = '\0';  // This signals it to use the default directory.
  8288.                 }
  8289.                 //else leave working_dir set to the entire clsid string in case it's somehow valid.
  8290.         }
  8291.         // else it is a directory, so just leave working_dir set as it was initially.
  8292.     }
  8293.  
  8294.     char greeting[1024];
  8295.     if (aGreeting && *aGreeting)
  8296.         strlcpy(greeting, aGreeting, sizeof(greeting));
  8297.     else
  8298.         // Use a more specific title so that the dialogs of different scripts can be distinguished
  8299.         // from one another, which may help script automation in rare cases:
  8300.         snprintf(greeting, sizeof(greeting), "Select File - %s", g_script.mFileName);
  8301.  
  8302.     // The filter must be terminated by two NULL characters.  One is explicit, the other automatic:
  8303.     char filter[1024] = "", pattern[1024] = "";  // Set default.
  8304.     if (*aFilter)
  8305.     {
  8306.         char *pattern_start = strchr(aFilter, '(');
  8307.         if (pattern_start)
  8308.         {
  8309.             // Make pattern a separate string because we want to remove any spaces from it.
  8310.             // For example, if the user specified Documents (*.txt; *.doc), the space after
  8311.             // the semicolon should be removed for the pattern string itself but not from
  8312.             // the displayed version of the pattern:
  8313.             strlcpy(pattern, ++pattern_start, sizeof(pattern));
  8314.             char *pattern_end = strrchr(pattern, ')'); // strrchr() in case there are other literal parentheses.
  8315.             if (pattern_end)
  8316.                 *pattern_end = '\0';  // If parentheses are empty, this will set pattern to be the empty string.
  8317.             else // no closing paren, so set to empty string as an indicator:
  8318.                 *pattern = '\0';
  8319.  
  8320.         }
  8321.         else // No open-paren, so assume the entire string is the filter.
  8322.             strlcpy(pattern, aFilter, sizeof(pattern));
  8323.         if (*pattern)
  8324.         {
  8325.             // Remove any spaces present in the pattern, such as a space after every semicolon
  8326.             // that separates the allowed file extensions.  The API docs specify that there
  8327.             // should be no spaces in the pattern itself, even though it's okay if they exist
  8328.             // in the displayed name of the file-type:
  8329.             StrReplace(pattern, " ", "", SCS_SENSITIVE);
  8330.             // Also include the All Files (*.*) filter, since there doesn't seem to be much
  8331.             // point to making this an option.  This is because the user could always type
  8332.             // *.* and press ENTER in the filename field and achieve the same result:
  8333.             snprintf(filter, sizeof(filter), "%s%c%s%cAll Files (*.*)%c*.*%c"
  8334.                 , aFilter, '\0', pattern, '\0', '\0', '\0'); // The final '\0' double-terminates by virtue of the fact that snprintf() itself provides a final terminator.
  8335.         }
  8336.         else
  8337.             *filter = '\0';  // It will use a standard default below.
  8338.     }
  8339.  
  8340.     OPENFILENAME ofn = {0};
  8341.     // OPENFILENAME_SIZE_VERSION_400 must be used for 9x/NT otherwise the dialog will not appear!
  8342.     // MSDN: "In an application that is compiled with WINVER and _WIN32_WINNT >= 0x0500, use
  8343.     // OPENFILENAME_SIZE_VERSION_400 for this member.  Windows 2000/XP: Use sizeof(OPENFILENAME)
  8344.     // for this parameter."
  8345.     ofn.lStructSize = g_os.IsWin2000orLater() ? sizeof(OPENFILENAME) : OPENFILENAME_SIZE_VERSION_400;
  8346.     ofn.hwndOwner = THREAD_DIALOG_OWNER; // Can be NULL, which is used instead of main window since no need to have main window forced into the background for this.
  8347.     ofn.lpstrTitle = greeting;
  8348.     ofn.lpstrFilter = *filter ? filter : "All Files (*.*)\0*.*\0Text Documents (*.txt)\0*.txt\0";
  8349.     ofn.lpstrFile = file_buf;
  8350.     ofn.nMaxFile = sizeof(file_buf) - 1; // -1 to be extra safe.
  8351.     // Specifying NULL will make it default to the last used directory (at least in Win2k):
  8352.     ofn.lpstrInitialDir = *working_dir ? working_dir : NULL;
  8353.  
  8354.     // Note that the OFN_NOCHANGEDIR flag is ineffective in some cases, so we'll use a custom
  8355.     // workaround instead.  MSDN: "Windows NT 4.0/2000/XP: This flag is ineffective for GetOpenFileName."
  8356.     // In addition, it does not prevent the CWD from changing while the user navigates from folder to
  8357.     // folder in the dialog, except perhaps on Win9x.
  8358.  
  8359.     // For v1.0.25.05, the new "M" letter is used for a new multi-select method since the old multi-select
  8360.     // is faulty in the following ways:
  8361.     // 1) If the user selects a single file in a multi-select dialog, the result is inconsistent: it
  8362.     //    contains the full path and name of that single file rather than the folder followed by the
  8363.     //    single file name as most users would expect.  To make matters worse, it includes a linefeed
  8364.     //    after that full path in name, which makes it difficult for a script to determine whether
  8365.     //    only a single file was selected.
  8366.     // 2) The last item in the list is terminated by a linefeed, which is not as easily used with a
  8367.     //    parsing loop as shown in example in the help file.
  8368.     bool always_use_save_dialog = false; // Set default.
  8369.     bool new_multi_select_method = false; // Set default.
  8370.     switch (toupper(*aOptions))
  8371.     {
  8372.     case 'M':  // Multi-select.
  8373.         ++aOptions;
  8374.         new_multi_select_method = true;
  8375.         break;
  8376.     case 'S': // Have a "Save" button rather than an "Open" button.
  8377.         ++aOptions;
  8378.         always_use_save_dialog = true;
  8379.         break;
  8380.     }
  8381.  
  8382.     int options = ATOI(aOptions);
  8383.     // v1.0.43.09: OFN_NODEREFERENCELINKS is now omitted by default because most people probably want a click
  8384.     // on a shortcut to navigate to the shortcut's target rather than select the shortcut and end the dialog.
  8385.     ofn.Flags = OFN_HIDEREADONLY | OFN_EXPLORER;  // OFN_HIDEREADONLY: Hides the Read Only check box.
  8386.     if (options & 0x20) // v1.0.43.09.
  8387.         ofn.Flags |= OFN_NODEREFERENCELINKS;
  8388.     if (options & 0x10)
  8389.         ofn.Flags |= OFN_OVERWRITEPROMPT;
  8390.     if (options & 0x08)
  8391.         ofn.Flags |= OFN_CREATEPROMPT;
  8392.     if (new_multi_select_method || (options & 0x04))
  8393.         ofn.Flags |= OFN_ALLOWMULTISELECT;
  8394.     if (options & 0x02)
  8395.         ofn.Flags |= OFN_PATHMUSTEXIST;
  8396.     if (options & 0x01)
  8397.         ofn.Flags |= OFN_FILEMUSTEXIST;
  8398.  
  8399.     // At this point, we know a dialog will be displayed.  See macro's comments for details:
  8400.     DIALOG_PREP
  8401.     POST_AHK_DIALOG(0) // Do this only after the above. Must pass 0 for timeout in this case.
  8402.  
  8403.     ++g_nFileDialogs;
  8404.     // Below: OFN_CREATEPROMPT doesn't seem to work with GetSaveFileName(), so always
  8405.     // use GetOpenFileName() in that case:
  8406.     BOOL result = (always_use_save_dialog || ((ofn.Flags & OFN_OVERWRITEPROMPT) && !(ofn.Flags & OFN_CREATEPROMPT)))
  8407.         ? GetSaveFileName(&ofn) : GetOpenFileName(&ofn);
  8408.     --g_nFileDialogs;
  8409.  
  8410.     DIALOG_END
  8411.  
  8412.     // Both GetOpenFileName() and GetSaveFileName() change the working directory as a side-effect
  8413.     // of their operation.  The below is not a 100% workaround for the problem because even while
  8414.     // a new quasi-thread is running (having interrupted this one while the dialog is still
  8415.     // displayed), the dialog is still functional, and as a result, the dialog changes the
  8416.     // working directory every time the user navigates to a new folder.
  8417.     // This is only needed when the user pressed OK, since the dialog auto-restores the
  8418.     // working directory if CANCEL is pressed or the window was closed by means other than OK.
  8419.     // UPDATE: No, it's needed for CANCEL too because GetSaveFileName/GetOpenFileName will restore
  8420.     // the working dir to the wrong dir if the user changed it (via SetWorkingDir) while the
  8421.     // dialog was displayed.
  8422.     // Restore the original working directory so that any threads suspended beneath this one,
  8423.     // and any newly launched ones if there aren't any suspended threads, will have the directory
  8424.     // that the user expects.  NOTE: It's possible for g_WorkingDir to have changed via the
  8425.     // SetWorkingDir command while the dialog was displayed (e.g. a newly launched quasi-thread):
  8426.     if (*g_WorkingDir)
  8427.         SetCurrentDirectory(g_WorkingDir);
  8428.  
  8429.     if (!result) // User pressed CANCEL vs. OK to dismiss the dialog or there was a problem displaying it.
  8430.         // It seems best to clear the variable in these cases, since this is a scripting
  8431.         // language where performance is not the primary goal.  So do that and return OK,
  8432.         // but leave ErrorLevel set to ERRORLEVEL_ERROR.
  8433.         return output_var.Assign(); // Tell it not to free the memory by not calling with "".
  8434.     else
  8435.         g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate that the user pressed OK vs. CANCEL.
  8436.  
  8437.     if (ofn.Flags & OFN_ALLOWMULTISELECT)
  8438.     {
  8439.         char *cp;
  8440.         if (new_multi_select_method) // v1.0.25.05+ method.
  8441.         {
  8442.             // If the first terminator in file_buf is also the last, the user selected only
  8443.             // a single file:
  8444.             size_t length = strlen(file_buf);
  8445.             if (!file_buf[length + 1]) // The list contains only a single file (full path and name).
  8446.             {
  8447.                 // v1.0.25.05: To make the result of selecting one file the same as selecting multiple files
  8448.                 // -- and thus easier to work with in a script -- convert the result into the multi-file
  8449.                 // format (folder as first item and naked filename as second):
  8450.                 if (cp = strrchr(file_buf, '\\'))
  8451.                 {
  8452.                     *cp = '\n';
  8453.                     // If the folder is the root folder, add a backslash so that selecting a single
  8454.                     // file yields the same reported folder as selecting multiple files.  One reason
  8455.                     // for doing it this way is that SetCurrentDirectory() requires a backslash after
  8456.                     // a root folder to succeed.  This allows a script to use SetWorkingDir to change
  8457.                     // to the selected folder before operating on each of the selected/naked filenames.
  8458.                     if (cp - file_buf == 2 && cp[-1] == ':') // e.g. "C:"
  8459.                     {
  8460.                         memmove(cp + 1, cp, strlen(cp) + 1); // Make room to insert backslash (since only one file was selcted, the buf is large enough).
  8461.                         *cp = '\\';
  8462.                     }
  8463.                 }
  8464.             }
  8465.             else // More than one file was selected.
  8466.             {
  8467.                 // Use the same method as the old multi-select format except don't provide a
  8468.                 // linefeed after the final item.  That final linefeed would make parsing via
  8469.                 // a parsing loop more complex because a parsing loop would see a blank item
  8470.                 // at the end of the list:
  8471.                 for (cp = file_buf;;)
  8472.                 {
  8473.                     for (; *cp; ++cp); // Find the next terminator.
  8474.                     if (!cp[1]) // This is the last file because it's double-terminated, so we're done.
  8475.                         break;
  8476.                     *cp = '\n'; // Replace zero-delimiter with a visible/printable delimiter, for the user.
  8477.                 }
  8478.             }
  8479.         }
  8480.         else  // Old multi-select method is in effect (kept for backward compatibility).
  8481.         {
  8482.             // Replace all the zero terminators with a delimiter, except the one for the last file
  8483.             // (the last file should be followed by two sequential zero terminators).
  8484.             // Use a delimiter that can't be confused with a real character inside a filename, i.e.
  8485.             // not a comma.  We only have room for one without getting into the complexity of having
  8486.             // to expand the string, so \r\n is disqualified for now.
  8487.             for (cp = file_buf;;)
  8488.             {
  8489.                 for (; *cp; ++cp); // Find the next terminator.
  8490.                 *cp = '\n'; // Replace zero-delimiter with a visible/printable delimiter, for the user.
  8491.                 if (!cp[1]) // This is the last file because it's double-terminated, so we're done.
  8492.                     break;
  8493.             }
  8494.         }
  8495.     }
  8496.     return output_var.Assign(file_buf);
  8497. }
  8498.  
  8499.  
  8500.  
  8501. ResultType Line::FileCreateDir(char *aDirSpec)
  8502. {
  8503.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  8504.     if (!aDirSpec || !*aDirSpec)
  8505.         return OK;  // Return OK because g_ErrorLevel tells the story.
  8506.  
  8507.     DWORD attr = GetFileAttributes(aDirSpec);
  8508.     if (attr != 0xFFFFFFFF)  // aDirSpec already exists.
  8509.     {
  8510.         if (attr & FILE_ATTRIBUTE_DIRECTORY)
  8511.             g_ErrorLevel->Assign(ERRORLEVEL_NONE);  // Indicate success since it already exists as a dir.
  8512.         // else leave as failure, since aDirSpec exists as a file, not a dir.
  8513.         return OK;
  8514.     }
  8515.  
  8516.     // If it has a backslash, make sure all its parent directories exist before we attempt
  8517.     // to create this directory:
  8518.     char *last_backslash = strrchr(aDirSpec, '\\');
  8519.     if (last_backslash)
  8520.     {
  8521.         char parent_dir[MAX_PATH];
  8522.         if (strlen(aDirSpec) >= sizeof(parent_dir)) // avoid overflow
  8523.             return OK; // Let ErrorLevel tell the story.
  8524.         strlcpy(parent_dir, aDirSpec, last_backslash - aDirSpec + 1); // Omits the last backslash.
  8525.         FileCreateDir(parent_dir); // Recursively create all needed ancestor directories.
  8526.  
  8527.         // v1.0.44: Fixed ErrorLevel being set to 1 when the specified directory ends in a backslash.  In such cases,
  8528.         // two calls were made to CreateDirectory for the same folder: the first without the backslash and then with
  8529.         // it.  Since the directory already existed on the second call, ErrorLevel was wrongly set to 1 even though
  8530.         // everything succeeded.  So now, when recursion finishes creating all the ancestors of this directory
  8531.         // our own layer here does not call CreateDirectory() when there's a trailing backslash because a previous
  8532.         // layer already did:
  8533.         if (!last_backslash[1] || *g_ErrorLevel->Contents() == *ERRORLEVEL_ERROR)
  8534.             return OK; // Let the previously set ErrorLevel (whatever it is) tell the story.
  8535.     }
  8536.  
  8537.     // The above has recursively created all parent directories of aDirSpec if needed.
  8538.     // Now we can create aDirSpec.  Be sure to explicitly set g_ErrorLevel since it's value
  8539.     // is now indeterminate due to action above:
  8540.     return g_ErrorLevel->Assign(CreateDirectory(aDirSpec, NULL) ? ERRORLEVEL_NONE : ERRORLEVEL_ERROR);
  8541. }
  8542.  
  8543.  
  8544.  
  8545. ResultType Line::FileRead(char *aFilespec)
  8546. // Returns OK or FAIL.  Will almost always return OK because if an error occurs,
  8547. // the script's ErrorLevel variable will be set accordingly.  However, if some
  8548. // kind of unexpected and more serious error occurs, such as variable-out-of-memory,
  8549. // that will cause FAIL to be returned.
  8550. {
  8551.     Var &output_var = *OUTPUT_VAR;
  8552.     // Init output var to be blank as an additional indicator of failure (or empty file).
  8553.     // Caller must check ErrorLevel to distinguish between an empty file and an error.
  8554.     output_var.Assign();
  8555.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  8556.  
  8557.     // The program is currently compiled with a 2GB address limit, so loading files larger than that
  8558.     // would probably fail or perhaps crash the program.  Therefore, just putting a basic 1.0 GB sanity
  8559.     // limit on the file here, for now.  Note: a variable can still be operated upon without necessarily
  8560.     // using the deref buffer, since that buffer only comes into play when there is no other way to
  8561.     // manipulate the variable.  In other words, the deref buffer won't necessarily grow to be the same
  8562.     // size as the file, which if it happened for a 1GB file would exceed the 2GB address limit.
  8563.     // That is why a smaller limit such as 800 MB seems too severe:
  8564.     #define FILEREAD_MAX (1024*1024*1024) // 1 GB.
  8565.  
  8566.     // Set default options:
  8567.     bool translate_crlf_to_lf = false;
  8568.     bool is_binary_clipboard = false;
  8569.     unsigned __int64 max_bytes_to_load = ULLONG_MAX;
  8570.  
  8571.     // It's done as asterisk+option letter to permit future expansion.  A plain asterisk such as used
  8572.     // by the FileAppend command would create ambiguity if there was ever an effort to add other asterisk-
  8573.     // prefixed options later.
  8574.     char *cp;
  8575.     for (;;)
  8576.     {
  8577.         cp = omit_leading_whitespace(aFilespec); // omit leading whitespace only temporarily in case aFilespec contains literal whitespace we want to retain.
  8578.         if (*cp != '*') // No more options.
  8579.             break; // Make no further changes to aFilespec.
  8580.         switch (toupper(*++cp)) // This could move cp to the terminator if string ends in an asterisk.
  8581.         {
  8582.         case 'C': // Clipboard (binary).
  8583.             is_binary_clipboard = true; // When this option is present, any others are parsed (to skip over them) but ignored as documented.
  8584.             break;
  8585.         case 'M': // Maximum number of bytes to load.
  8586.             max_bytes_to_load = ATOU64(cp + 1); // Relies upon the fact that it ceases conversion upon reaching a space or tab.
  8587.             if (max_bytes_to_load > FILEREAD_MAX) // Force a failure to avoid breaking scripts if this limit is increased in the future.
  8588.                 return OK; // Let ErrorLevel tell the story.
  8589.             // Skip over the digits of this option in case it's the last option.
  8590.             if (   !(cp = StrChrAny(cp, " \t"))   ) // Find next space or tab (there should be one if options are properly formatted).
  8591.                 return OK; // Let ErrorLevel tell the story.
  8592.             --cp; // Standardize it to make it conform to the other options, for use below.
  8593.             break;
  8594.         case 'T': // Text mode.
  8595.             translate_crlf_to_lf = true;
  8596.             break;
  8597.         }
  8598.         // Note: because it's possible for filenames to start with a space (even though Explorer itself
  8599.         // won't let you create them that way), allow exactly one space between end of option and the
  8600.         // filename itself:
  8601.         aFilespec = cp;  // aFilespec is now the option letter after the asterisk *or* empty string if there was none.
  8602.         if (*aFilespec)
  8603.         {
  8604.             ++aFilespec;
  8605.             // Now it's the space or tab (if there is one) after the option letter.  It seems best for
  8606.             // future expansion to assume that this is a space or tab even if it's really the start of
  8607.             // the filename.  For example, in the future, multiletter options might be wanted, in which
  8608.             // case allowing the omission of the space or tab between *t and the start of the filename
  8609.             // would cause the following to be ambiguous:
  8610.             // FileRead, OutputVar, *delimC:\File.txt
  8611.             // (assuming *delim would accept an optional arg immediately following it).
  8612.             // Enforcing this format also simplifies parsing in the future, if there are ever multiple options.
  8613.             // It also conforms to the precedent/behavior of GuiControl when it accepts picture sizing options
  8614.             // such as *w/h and *x/y
  8615.             if (*aFilespec)
  8616.                 ++aFilespec; // And now it's the start of the filename or the asterisk of the next option.
  8617.                             // This behavior is as documented in the help file.
  8618.         }
  8619.     } // for()
  8620.  
  8621.     // It seems more flexible to allow other processes to read and write the file while we're reading it.
  8622.     // For example, this allows the file to be appended to during the read operation, which could be
  8623.     // desirable, especially it's a very large log file that would take a long time to read.
  8624.     // MSDN: "To enable other processes to share the object while your process has it open, use a combination
  8625.     // of one or more of [FILE_SHARE_READ, FILE_SHARE_WRITE]."
  8626.     HANDLE hfile = CreateFile(aFilespec, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING
  8627.         , FILE_FLAG_SEQUENTIAL_SCAN, NULL); // MSDN says that FILE_FLAG_SEQUENTIAL_SCAN will often improve performance
  8628.     if (hfile == INVALID_HANDLE_VALUE)      // in cases like these (and it seems best even if max_bytes_to_load was specified).
  8629.         return OK; // Let ErrorLevel tell the story.
  8630.  
  8631.     if (is_binary_clipboard && output_var.Type() == VAR_CLIPBOARD)
  8632.         return ReadClipboardFromFile(hfile);
  8633.     // Otherwise, if is_binary_clipboard, load it directly into a normal variable.  The data in the
  8634.     // clipboard file should already have the (UINT)0 as its ending terminator.
  8635.  
  8636.     unsigned __int64 bytes_to_read = GetFileSize64(hfile);
  8637.     if (bytes_to_read == ULLONG_MAX // GetFileSize64() failed...
  8638.         || max_bytes_to_load == ULLONG_MAX && bytes_to_read > FILEREAD_MAX) // ...or the file is too large to be completely read (and the script wanted it completely read).
  8639.     {
  8640.         CloseHandle(hfile);
  8641.         return OK; // Let ErrorLevel tell the story.
  8642.     }
  8643.     if (max_bytes_to_load < bytes_to_read)
  8644.         bytes_to_read = max_bytes_to_load;
  8645.  
  8646.     g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Set default for this point forward to be "success".
  8647.  
  8648.     if (!bytes_to_read)
  8649.     {
  8650.         CloseHandle(hfile);
  8651.         return OK; // And ErrorLevel will indicate success (a zero-length file results in empty output_var).
  8652.     }
  8653.  
  8654.     // Set up the var, enlarging it if necessary.  If the output_var is of type VAR_CLIPBOARD,
  8655.     // this call will set up the clipboard for writing:
  8656.     if (output_var.Assign(NULL, (VarSizeType)bytes_to_read, true, false) != OK) // Probably due to "out of memory".
  8657.     {
  8658.         CloseHandle(hfile);
  8659.         return FAIL;  // It already displayed the error. ErrorLevel doesn't matter now because the current quasi-thread will be aborted.
  8660.     }
  8661.     char *output_buf = output_var.Contents();
  8662.  
  8663.     DWORD bytes_actually_read;
  8664.     BOOL result = ReadFile(hfile, output_buf, (DWORD)bytes_to_read, &bytes_actually_read, NULL);
  8665.     CloseHandle(hfile);
  8666.  
  8667.     // Upon result==success, bytes_actually_read is not checked against bytes_to_read because it
  8668.     // shouldn't be different (result should have set to failure if there was a read error).
  8669.     // If it ever is different, a partial read is considered a success since ReadFile() told us
  8670.     // that nothing bad happened.
  8671.  
  8672.     if (result)
  8673.     {
  8674.         output_buf[bytes_actually_read] = '\0';  // Ensure text is terminated where indicated.
  8675.         // Since a larger string is being replaced with a smaller, there's a good chance the 2 GB
  8676.         // address limit will not be exceeded by StrReplace even if the file is close to the
  8677.         // 1 GB limit as described above:
  8678.         if (translate_crlf_to_lf)
  8679.             StrReplace(output_buf, "\r\n", "\n", SCS_SENSITIVE); // Safe only because larger string is being replaced with smaller.
  8680.         output_var.Length() = is_binary_clipboard ? (bytes_actually_read - 1) // Length excludes the very last byte of the (UINT)0 terminator.
  8681.             : (VarSizeType)strlen(output_buf); // In case file contains binary zeroes, explicitly calculate the "usable" length so that it's accurate.
  8682.     }
  8683.     else
  8684.     {
  8685.         // ReadFile() failed.  Since MSDN does not document what is in the buffer at this stage,
  8686.         // or whether what's in it is even null-terminated, or even whether bytes_to_read contains
  8687.         // a valid value, it seems best to abort the entire operation rather than try to put partial
  8688.         // file contents into output_var.  ErrorLevel will indicate the failure.
  8689.         // Since ReadFile() failed, to avoid complications or side-effects in functions such as Var::Close(),
  8690.         // avoid storing a potentially non-terminated string in the variable.
  8691.         *output_buf = '\0';
  8692.         output_var.Length() = 0;
  8693.         g_ErrorLevel->Assign(ERRORLEVEL_ERROR);  // Override the success default that was set in the middle of this function.
  8694.     }
  8695.  
  8696.     // ErrorLevel, as set in various places above, indicates success or failure.
  8697.     return output_var.Close(is_binary_clipboard);
  8698. }
  8699.  
  8700.  
  8701.  
  8702. ResultType Line::FileReadLine(char *aFilespec, char *aLineNumber)
  8703. // Returns OK or FAIL.  Will almost always return OK because if an error occurs,
  8704. // the script's ErrorLevel variable will be set accordingly.  However, if some
  8705. // kind of unexpected and more serious error occurs, such as variable-out-of-memory,
  8706. // that will cause FAIL to be returned.
  8707. {
  8708.     Var &output_var = *OUTPUT_VAR; // Fix for v1.0.45.01: Must be resolved and saved before MsgSleep() (LONG_OPERATION) because that allows some other thread to interrupt and overwrite sArgVar[].
  8709.  
  8710.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  8711.     __int64 line_number = ATOI64(aLineNumber);
  8712.     if (line_number < 1)
  8713.         return OK;  // Return OK because g_ErrorLevel tells the story.
  8714.     FILE *fp = fopen(aFilespec, "r");
  8715.     if (!fp)
  8716.         return OK;  // Return OK because g_ErrorLevel tells the story.
  8717.  
  8718.     // Remember that once the first call to MsgSleep() is done, a new hotkey subroutine
  8719.     // may fire and suspend what we're doing here.  Such a subroutine might also overwrite
  8720.     // the values our params, some of which may be in the deref buffer.  So be sure not
  8721.     // to refer to those strings once MsgSleep() has been done, below.  Alternatively,
  8722.     // a copy of such params can be made using our own stack space.
  8723.  
  8724.     LONG_OPERATION_INIT
  8725.  
  8726.     char buf[READ_FILE_LINE_SIZE];
  8727.     for (__int64 i = 0; i < line_number; ++i)
  8728.     {
  8729.         if (fgets(buf, sizeof(buf) - 1, fp) == NULL) // end-of-file or error
  8730.         {
  8731.             fclose(fp);
  8732.             return OK;  // Return OK because g_ErrorLevel tells the story.
  8733.         }
  8734.         LONG_OPERATION_UPDATE
  8735.     }
  8736.     fclose(fp);
  8737.  
  8738.     size_t buf_length = strlen(buf);
  8739.     if (buf_length && buf[buf_length - 1] == '\n') // Remove any trailing newline for the user.
  8740.         buf[--buf_length] = '\0';
  8741.     if (!buf_length)
  8742.     {
  8743.         if (!output_var.Assign()) // Explicitly call it this way so that it won't free the memory.
  8744.             return FAIL;
  8745.     }
  8746.     else
  8747.         if (!output_var.Assign(buf, (VarSizeType)buf_length))
  8748.             return FAIL;
  8749.     return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  8750. }
  8751.  
  8752.  
  8753.  
  8754. ResultType Line::FileAppend(char *aFilespec, char *aBuf, LoopReadFileStruct *aCurrentReadFile)
  8755. {
  8756.     // The below is avoided because want to allow "nothing" to be written to a file in case the
  8757.     // user is doing this to reset it's timestamp (or create an empty file).
  8758.     //if (!aBuf || !*aBuf)
  8759.     //    return g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  8760.  
  8761.     if (aCurrentReadFile) // It always takes precedence over aFilespec.
  8762.         aFilespec = aCurrentReadFile->mWriteFileName;
  8763.     if (!*aFilespec) // Nothing to write to (caller relies on this check).
  8764.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  8765.  
  8766.     FILE *fp = aCurrentReadFile ? aCurrentReadFile->mWriteFile : NULL;
  8767.     bool file_was_already_open = fp;
  8768.  
  8769.     bool open_as_binary = (*aFilespec == '*');
  8770.     if (open_as_binary && !*(aFilespec + 1)) // Naked "*" means write to stdout.
  8771.         // Avoid puts() in case it bloats the code in some compilers. i.e. fputs() is already used,
  8772.         // so using it again here shouldn't bloat it:
  8773.         return g_ErrorLevel->Assign(fputs(aBuf, stdout) ? ERRORLEVEL_ERROR : ERRORLEVEL_NONE); // fputs() returns 0 on success.
  8774.         
  8775.     if (open_as_binary)
  8776.         // Do not do this because it's possible for filenames to start with a space
  8777.         // (even though Explorer itself won't let you create them that way):
  8778.         //write_filespec = omit_leading_whitespace(write_filespec + 1);
  8779.         // Instead just do this:
  8780.         ++aFilespec;
  8781.     else if (!file_was_already_open) // As of 1.0.25, auto-detect binary if that mode wasn't explicitly specified.
  8782.     {
  8783.         // sArgVar is used for two reasons:
  8784.         // 1) It properly resolves dynamic variables, such as "FileAppend, %VarContainingTheStringClipboardAll%, File".
  8785.         // 2) It resolves them only once at a prior stage, rather than having to do them again here
  8786.         //    (which helps performance).
  8787.         if (ARGVAR1)
  8788.         {
  8789.             if (ARGVAR1->Type() == VAR_CLIPBOARDALL)
  8790.                 return WriteClipboardToFile(aFilespec);
  8791.             else if (ARGVAR1->IsBinaryClip())
  8792.             {
  8793.                 // Since there is at least one deref in Arg #1 and the first deref is binary clipboard,
  8794.                 // assume this operation's only purpose is to write binary data from that deref to a file.
  8795.                 // This is because that's the only purpose that seems useful and that's currently supported.
  8796.                 // In addition, the file is always overwritten in this mode, since appending clipboard data
  8797.                 // to an existing clipboard file would not work due to:
  8798.                 // 1) Duplicate clipboard formats not making sense (i.e. two CF_TEXT formats would cause the
  8799.                 //    first to be overwritten by the second when restoring to clipboard).
  8800.                 // 2) There is a 4-byte zero terminator at the end of the file.
  8801.                 if (   !(fp = fopen(aFilespec, "wb"))   ) // Overwrite.
  8802.                     return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  8803.                 g_ErrorLevel->Assign(fwrite(ARGVAR1->Contents(), ARGVAR1->Length() + 1, 1, fp)
  8804.                     ? ERRORLEVEL_NONE : ERRORLEVEL_ERROR); // In this case, fwrite() will return 1 on success, 0 on failure.
  8805.                 fclose(fp);
  8806.                 return OK;
  8807.             }
  8808.         }
  8809.         // Auto-detection avoids the need to have to translate \r\n to \n when reading
  8810.         // a file via the FileRead command.  This seems extremely unlikely to break any
  8811.         // existing scripts because the intentional use of \r\r\n in a text file (two
  8812.         // consecutive carriage returns) -- which would happen if \r\n were written in
  8813.         // text mode -- is so rare as to be close to non-existent.  If this behavior
  8814.         // is ever specifically needed, the script can explicitly places some \r\r\n's
  8815.         // in the file and then write it as binary mode.
  8816.         open_as_binary = strstr(aBuf, "\r\n");
  8817.         // Due to "else if", the above will not turn off binary mode if binary was explicitly specified.
  8818.         // That is useful to write Unix style text files whose lines end in solitary linefeeds.
  8819.     }
  8820.  
  8821.     // Check if the file needes to be opened.  As of 1.0.25, this is done here rather than
  8822.     // at the time the loop first begins so that:
  8823.     // 1) Binary mode can be auto-detected if the first block of text appended to the file
  8824.     //    contains any \r\n's.
  8825.     // 2) To avoid opening the file if the file-reading loop has zero iterations (i.e. it's
  8826.     //    opened only upon first actual use to help performance and avoid changing the
  8827.     //    file-modification time when no actual text will be appended).
  8828.     if (!file_was_already_open)
  8829.     {
  8830.         // Open the output file (if one was specified).  Unlike the input file, this is not
  8831.         // a critical error if it fails.  We want it to be non-critical so that FileAppend
  8832.         // commands in the body of the loop will set ErrorLevel to indicate the problem:
  8833.         if (   !(fp = fopen(aFilespec, open_as_binary ? "ab" : "a"))   )
  8834.             return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  8835.         if (aCurrentReadFile)
  8836.             aCurrentReadFile->mWriteFile = fp;
  8837.     }
  8838.  
  8839.     // Write to the file:
  8840.     g_ErrorLevel->Assign(fputs(aBuf, fp) ? ERRORLEVEL_ERROR : ERRORLEVEL_NONE); // fputs() returns 0 on success.
  8841.  
  8842.     if (!aCurrentReadFile)
  8843.         fclose(fp);
  8844.     // else it's the caller's responsibility, or it's caller's, to close it.
  8845.  
  8846.     return OK;
  8847. }
  8848.  
  8849.  
  8850.  
  8851. ResultType Line::WriteClipboardToFile(char *aFilespec)
  8852. // Returns OK or FAIL.  If OK, it sets ErrorLevel to the appropriate result.
  8853. // If the clipboard is empty, a zero length file will be written, which seems best for its consistency.
  8854. {
  8855.     // This method used is very similar to that used in PerformAssign(), so see that section
  8856.     // for a large quantity of comments.
  8857.  
  8858.     if (!g_clip.Open())
  8859.         return LineError(CANT_OPEN_CLIPBOARD_READ); // Make this a critical/stop error since it's unusual and something the user would probably want to know about.
  8860.  
  8861.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default.
  8862.  
  8863.     HANDLE hfile = CreateFile(aFilespec, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); // Overwrite. Unsharable (since reading the file while it is being written would probably produce bad data in this case).
  8864.     if (hfile == INVALID_HANDLE_VALUE)
  8865.         return g_clip.Close(); // Let ErrorLevel tell the story.
  8866.  
  8867.     UINT format;
  8868.     HGLOBAL hglobal;
  8869.     LPVOID hglobal_locked;
  8870.     SIZE_T size;
  8871.     DWORD bytes_written;
  8872.     BOOL result;
  8873.     bool format_is_text, format_is_dib, format_is_meta;
  8874.     bool text_was_already_written = false, dib_was_already_written = false, meta_was_already_written = false;
  8875.  
  8876.     for (format = 0; format = EnumClipboardFormats(format);)
  8877.     {
  8878.         format_is_text = (format == CF_TEXT || format == CF_OEMTEXT || format == CF_UNICODETEXT);
  8879.         format_is_dib = (format == CF_DIB || format == CF_DIBV5);
  8880.         format_is_meta = (format == CF_ENHMETAFILE || format == CF_METAFILEPICT);
  8881.  
  8882.         // Only write one Text and one Dib format, omitting the others to save space.  See
  8883.         // similar section in PerformAssign() for details:
  8884.         if (format_is_text && text_was_already_written
  8885.             || format_is_dib && dib_was_already_written
  8886.             || format_is_meta && meta_was_already_written)
  8887.             continue;
  8888.  
  8889.         if (format_is_text)
  8890.             text_was_already_written = true;
  8891.         else if (format_is_dib)
  8892.             dib_was_already_written = true;
  8893.         else if (format_is_meta)
  8894.             meta_was_already_written = true;
  8895.  
  8896.         if ((hglobal = g_clip.GetClipboardDataTimeout(format)) // Relies on short-circuit boolean order:
  8897.             && (!(size = GlobalSize(hglobal)) || (hglobal_locked = GlobalLock(hglobal)))) // Size of zero or lock succeeded: Include this format.
  8898.         {
  8899.             if (!WriteFile(hfile, &format, sizeof(format), &bytes_written, NULL)
  8900.                 || !WriteFile(hfile, &size, sizeof(size), &bytes_written, NULL))
  8901.             {
  8902.                 if (size)
  8903.                     GlobalUnlock(hglobal); // hglobal not hglobal_locked.
  8904.                 break; // File might be in an incomplete state now, but that's okay because the reading process checks for that.
  8905.             }
  8906.  
  8907.             if (size)
  8908.             {
  8909.                 result = WriteFile(hfile, hglobal_locked, (DWORD)size, &bytes_written, NULL);
  8910.                 GlobalUnlock(hglobal); // hglobal not hglobal_locked.
  8911.                 if (!result)
  8912.                     break; // File might be in an incomplete state now, but that's okay because the reading process checks for that.
  8913.             }
  8914.             //else hglobal_locked is not valid, so don't reference it or unlock it. In other words, 0 bytes are written for this format.
  8915.         }
  8916.     }
  8917.  
  8918.     g_clip.Close();
  8919.  
  8920.     if (!format) // Since the loop was not terminated as a result of a failed WriteFile(), write the 4-byte terminator (otherwise, omit it to avoid further corrupting the file).
  8921.         if (WriteFile(hfile, &format, sizeof(format), &bytes_written, NULL))
  8922.             g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success (otherwise, leave it set to failure).
  8923.  
  8924.     CloseHandle(hfile);
  8925.     return OK; // Let ErrorLevel, set above, tell the story.
  8926. }
  8927.  
  8928.  
  8929.  
  8930. ResultType Line::ReadClipboardFromFile(HANDLE hfile)
  8931. // Returns OK or FAIL.  If OK, ErrorLevel is overridden from the callers ERRORLEVEL_ERROR setting to
  8932. // ERRORLEVEL_SUCCESS, if appropriate.  This function also closes hfile before returning.
  8933. // The method used here is very similar to that used in PerformAssign(), so see that section
  8934. // for a large quantity of comments.
  8935. {
  8936.     if (!g_clip.Open())
  8937.     {
  8938.         CloseHandle(hfile);
  8939.         return LineError(CANT_OPEN_CLIPBOARD_WRITE); // Make this a critical/stop error since it's unusual and something the user would probably want to know about.
  8940.     }
  8941.     EmptyClipboard(); // Failure is not checked for since it's probably impossible under these conditions.
  8942.  
  8943.     UINT format;
  8944.     HGLOBAL hglobal;
  8945.     LPVOID hglobal_locked;
  8946.     SIZE_T size;
  8947.     DWORD bytes_read;
  8948.  
  8949.     if (!ReadFile(hfile, &format, sizeof(format), &bytes_read, NULL) || bytes_read < sizeof(format))
  8950.     {
  8951.         g_clip.Close();
  8952.         CloseHandle(hfile);
  8953.         return OK; // Let ErrorLevel, set by the caller, tell the story.
  8954.     }
  8955.  
  8956.     while (format)
  8957.     {
  8958.         if (!ReadFile(hfile, &size, sizeof(size), &bytes_read, NULL) || bytes_read < sizeof(size))
  8959.             break; // Leave what's already on the clipboard intact since it might be better than nothing.
  8960.  
  8961.         if (   !(hglobal = GlobalAlloc(GMEM_MOVEABLE, size))   ) // size==0 is okay.
  8962.         {
  8963.             g_clip.Close();
  8964.             CloseHandle(hfile);
  8965.             return LineError(ERR_OUTOFMEM); // Short msg since so rare.
  8966.         }
  8967.  
  8968.         if (size) // i.e. Don't try to lock memory of size zero.  It won't work and it's not needed.
  8969.         {
  8970.             if (   !(hglobal_locked = GlobalLock(hglobal))   )
  8971.             {
  8972.                 GlobalFree(hglobal);
  8973.                 g_clip.Close();
  8974.                 CloseHandle(hfile);
  8975.                 return LineError("GlobalLock"); // Short msg since so rare.
  8976.             }
  8977.             if (!ReadFile(hfile, hglobal_locked, (DWORD)size, &bytes_read, NULL) || bytes_read < size)
  8978.             {
  8979.                 GlobalUnlock(hglobal);
  8980.                 GlobalFree(hglobal); // Seems best not to do SetClipboardData for incomplete format (especially without zeroing the unused portion of global_locked).
  8981.                 break; // Leave what's already on the clipboard intact since it might be better than nothing.
  8982.             }
  8983.             GlobalUnlock(hglobal);
  8984.         }
  8985.         //else hglobal is just an empty format, but store it for completeness/accuracy (e.g. CF_BITMAP).
  8986.  
  8987.         SetClipboardData(format, hglobal); // The system now owns hglobal.
  8988.  
  8989.         if (!ReadFile(hfile, &format, sizeof(format), &bytes_read, NULL) || bytes_read < sizeof(format))
  8990.             break;
  8991.     }
  8992.  
  8993.     g_clip.Close();
  8994.     CloseHandle(hfile);
  8995.  
  8996.     if (format) // The loop ended as a result of a file error.
  8997.         return OK; // Let ErrorLevel, set above, tell the story.
  8998.     else // Indicate success.
  8999.         return g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  9000. }
  9001.  
  9002.  
  9003.  
  9004. ResultType Line::FileDelete()
  9005. {
  9006.     // Below is done directly this way rather than passed in as args mainly to emphasize that
  9007.     // ArgLength() can safely be called in Line methods like this one (which is done further below).
  9008.     // It also may also slightly improve performance and reduce code size.
  9009.     char *aFilePattern = ARG1;
  9010.  
  9011.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel, namely that "one file couldn't be deleted".
  9012.     if (!*aFilePattern)
  9013.         return OK;  // Let ErrorLevel indicate an error, since this is probably not what the user intended.
  9014.  
  9015.     if (!StrChrAny(aFilePattern, "?*"))
  9016.     {
  9017.         if (DeleteFile(aFilePattern))
  9018.             g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  9019.         return OK; // ErrorLevel will indicate failure if the above didn't succeed.
  9020.     }
  9021.  
  9022.     // Otherwise aFilePattern contains wildcards, so we'll search for all matches and delete them.
  9023.     // Testing shows that the ANSI version of FindFirstFile() will not accept a path+pattern longer
  9024.     // than 256 or so, even if the pattern would match files whose names are short enough to be legal.
  9025.     // Therefore, as of v1.0.25, there is also a hard limit of MAX_PATH on all these variables.
  9026.     // MSDN confirms this in a vague way: "In the ANSI version of FindFirstFile(), [plpFileName] is
  9027.     // limited to MAX_PATH characters."
  9028.     if (ArgLength(1) >= MAX_PATH) // Checked early to simplify things later below.
  9029.         return OK; // Return OK because this is non-critical.  Due to rarity (and backward compatibility), it seems best leave ErrorLevel at 1 to indicate the problem
  9030.  
  9031.     LONG_OPERATION_INIT
  9032.     int failure_count = 0; // Set default.
  9033.  
  9034.     WIN32_FIND_DATA current_file;
  9035.     HANDLE file_search = FindFirstFile(aFilePattern, ¤t_file);
  9036.     if (file_search == INVALID_HANDLE_VALUE) // No matching files found.
  9037.         return g_ErrorLevel->Assign(0); // Deleting a wildcard pattern that matches zero files is a success.
  9038.  
  9039.     // Otherwise:
  9040.     char file_path[MAX_PATH];
  9041.     strcpy(file_path, aFilePattern); // Above has already confirmed this won't overflow.
  9042.  
  9043.     // Remove the filename and/or wildcard part.   But leave the trailing backslash on it for
  9044.     // consistency with below:
  9045.     size_t file_path_length;
  9046.     char *last_backslash = strrchr(file_path, '\\');
  9047.     if (last_backslash)
  9048.     {
  9049.         *(last_backslash + 1) = '\0'; // i.e. retain the trailing backslash.
  9050.         file_path_length = strlen(file_path);
  9051.     }
  9052.     else // Use current working directory, e.g. if user specified only *.*
  9053.     {
  9054.         *file_path = '\0';
  9055.         file_path_length = 0;
  9056.     }
  9057.  
  9058.     char *append_pos = file_path + file_path_length; // For performance, copy in the unchanging part only once.  This is where the changing part gets appended.
  9059.     size_t space_remaining = sizeof(file_path) - file_path_length - 1; // Space left in file_path for the changing part.
  9060.  
  9061.     do
  9062.     {
  9063.         // Since other script threads can interrupt during LONG_OPERATION_UPDATE, it's important that
  9064.         // this command not refer to sArgDeref[] and sArgVar[] anytime after an interruption becomes
  9065.         // possible. This is because an interrupting thread usually changes the values to something
  9066.         // inappropriate for this thread.
  9067.         LONG_OPERATION_UPDATE
  9068.         if (current_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) // skip any matching directories.
  9069.             continue;
  9070.         if (strlen(current_file.cFileName) > space_remaining)
  9071.         {
  9072.             // v1.0.45.03: Don't even try to operate upon truncated filenames in case they accidentally
  9073.             // match the name of a real/existing file.
  9074.             ++failure_count;
  9075.         }
  9076.         else
  9077.         {
  9078.             strcpy(append_pos, current_file.cFileName); // Above has ensured this won't overflow.
  9079.             if (!DeleteFile(file_path))
  9080.                 ++failure_count;
  9081.         }
  9082.     } while (FindNextFile(file_search, ¤t_file));
  9083.     FindClose(file_search);
  9084.  
  9085.     return g_ErrorLevel->Assign(failure_count); // i.e. indicate success if there were no failures.
  9086. }
  9087.  
  9088.  
  9089.  
  9090. ResultType Line::FileInstall(char *aSource, char *aDest, char *aFlag)
  9091. {
  9092.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  9093.     bool allow_overwrite = (ATOI(aFlag) == 1);
  9094. #ifdef AUTOHOTKEYSC
  9095.     if (!allow_overwrite && Util_DoesFileExist(aDest))
  9096.         return OK; // Let ErrorLevel tell the story.
  9097.     HS_EXEArc_Read oRead;
  9098.     // AutoIt3: Open the archive in this compiled exe.
  9099.     // Jon gave me some details about why a password isn't needed: "The code in those libararies will
  9100.     // only allow files to be extracted from the exe is is bound to (i.e the script that it was
  9101.     // compiled with).  There are various checks and CRCs to make sure that it can't be used to read
  9102.     // the files from any other exe that is passed."
  9103.     if (oRead.Open(g_script.mFileSpec, "") != HS_EXEARC_E_OK)
  9104.     {
  9105.         MsgBox(ERR_EXE_CORRUPTED, 0, g_script.mFileSpec); // Usually caused by virus corruption. Probably impossible since it was previously opened successfully to run the main script.
  9106.         return OK; // Let ErrorLevel tell the story.
  9107.     }
  9108.     // aSource should be the same as the "file id" used to originally compress the file
  9109.     // when it was compiled into an EXE.  So this should seek for the right file:
  9110.     int result = oRead.FileExtract(aSource, aDest);
  9111.     oRead.Close();
  9112.  
  9113.     // v1.0.46.15: The following is a fix for the fact that a compiled script (but not an uncompiled one)
  9114.     // that executes FileInstall somehow causes the Random command to generate the same series of random
  9115.     // numbers every time the script launches. Perhaps the answer lies somewhere in oRead's code --
  9116.     // something that somehow resets the static data used by init_genrand().
  9117.     RESEED_RANDOM_GENERATOR;
  9118.  
  9119.     if (result != HS_EXEARC_E_OK)
  9120.     {
  9121.         MsgBox(aSource, 0, "Could not extract file:");
  9122.         return OK; // Let ErrorLevel tell the story.
  9123.     }
  9124.     g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  9125. #else
  9126.     // v1.0.35.11: Must search in A_ScriptDir by default because that's where ahk2exe will search by default.
  9127.     // The old behavior was to search in A_WorkingDir, which seems pointless because ahk2exe would never
  9128.     // be able to use that value if the script changes it while running.
  9129.     SetCurrentDirectory(g_script.mFileDir);
  9130.     if (CopyFile(aSource, aDest, !allow_overwrite))
  9131.         g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  9132.     SetCurrentDirectory(g_WorkingDir); // Restore to proper value.
  9133. #endif
  9134.     return OK;
  9135. }
  9136.  
  9137.  
  9138.  
  9139. ResultType Line::FileGetAttrib(char *aFilespec)
  9140. {
  9141.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default
  9142.     OUTPUT_VAR->Assign(); // Init to be blank, in case of failure.
  9143.  
  9144.     if (!aFilespec || !*aFilespec)
  9145.         return OK;  // Let ErrorLevel indicate an error, since this is probably not what the user intended.
  9146.  
  9147.     DWORD attr = GetFileAttributes(aFilespec);
  9148.     if (attr == 0xFFFFFFFF)  // Failure, probably because file doesn't exist.
  9149.         return OK;  // Let ErrorLevel tell the story.
  9150.  
  9151.     g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  9152.     char attr_string[128];
  9153.     return OUTPUT_VAR->Assign(FileAttribToStr(attr_string, attr));
  9154. }
  9155.  
  9156.  
  9157.  
  9158. int Line::FileSetAttrib(char *aAttributes, char *aFilePattern, FileLoopModeType aOperateOnFolders
  9159.     , bool aDoRecurse, bool aCalledRecursively)
  9160. // Returns the number of files and folders that could not be changed due to an error.
  9161. {
  9162.     if (!aCalledRecursively)  // i.e. Only need to do this if we're not called by ourself:
  9163.     {
  9164.         g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default
  9165.         if (!*aFilePattern)
  9166.             return 0;  // Let ErrorLevel indicate an error, since this is probably not what the user intended.
  9167.         if (aOperateOnFolders == FILE_LOOP_INVALID) // In case runtime dereference of a var was an invalid value.
  9168.             aOperateOnFolders = FILE_LOOP_FILES_ONLY;  // Set default.
  9169.     }
  9170.  
  9171.     if (strlen(aFilePattern) >= MAX_PATH) // Checked early to simplify other things below.
  9172.         return 0; // Let the above ErrorLevel indicate the problem.
  9173.  
  9174.     // Related to the comment at the top: Since the script subroutine that resulted in the call to
  9175.     // this function can be interrupted during our MsgSleep(), make a copy of any params that might
  9176.     // currently point directly to the deref buffer.  This is done because their contents might
  9177.     // be overwritten by the interrupting subroutine:
  9178.     char attributes[64];
  9179.     strlcpy(attributes, aAttributes, sizeof(attributes));
  9180.  
  9181.     // Testing shows that the ANSI version of FindFirstFile() will not accept a path+pattern longer
  9182.     // than 256 or so, even if the pattern would match files whose names are short enough to be legal.
  9183.     // Therefore, as of v1.0.25, there is also a hard limit of MAX_PATH on all these variables.
  9184.     // MSDN confirms this in a vague way: "In the ANSI version of FindFirstFile(), [plpFileName] is
  9185.     // limited to MAX_PATH characters."
  9186.     char file_pattern[MAX_PATH], file_path[MAX_PATH]; // Giving +3 extra for "*.*" seems fairly pointless because any files that actually need that extra room would fail to be retrieved by FindFirst/Next due to their inability to support paths much over 256.
  9187.     strcpy(file_pattern, aFilePattern); // Make a copy in case of overwrite of deref buf during LONG_OPERATION/MsgSleep.
  9188.     strcpy(file_path, aFilePattern);    // An earlier check has ensured these won't overflow.
  9189.  
  9190.     size_t file_path_length; // The length of just the path portion of the filespec.
  9191.     char *last_backslash = strrchr(file_path, '\\');
  9192.     if (last_backslash)
  9193.     {
  9194.         // Remove the filename and/or wildcard part.   But leave the trailing backslash on it for
  9195.         // consistency with below:
  9196.         *(last_backslash + 1) = '\0';
  9197.         file_path_length = strlen(file_path);
  9198.     }
  9199.     else // Use current working directory, e.g. if user specified only *.*
  9200.     {
  9201.         *file_path = '\0';
  9202.         file_path_length = 0;
  9203.     }
  9204.     char *append_pos = file_path + file_path_length; // For performance, copy in the unchanging part only once.  This is where the changing part gets appended.
  9205.     size_t space_remaining = sizeof(file_path) - file_path_length - 1; // Space left in file_path for the changing part.
  9206.  
  9207.     // For use with aDoRecurse, get just the naked file name/pattern:
  9208.     char *naked_filename_or_pattern = strrchr(file_pattern, '\\');
  9209.     if (naked_filename_or_pattern)
  9210.         ++naked_filename_or_pattern;
  9211.     else
  9212.         naked_filename_or_pattern = file_pattern;
  9213.  
  9214.     if (!StrChrAny(naked_filename_or_pattern, "?*"))
  9215.         // Since no wildcards, always operate on this single item even if it's a folder.
  9216.         aOperateOnFolders = FILE_LOOP_FILES_AND_FOLDERS;
  9217.  
  9218.     char *cp;
  9219.     enum attrib_modes {ATTRIB_MODE_NONE, ATTRIB_MODE_ADD, ATTRIB_MODE_REMOVE, ATTRIB_MODE_TOGGLE};
  9220.     attrib_modes mode = ATTRIB_MODE_NONE;
  9221.  
  9222.     LONG_OPERATION_INIT
  9223.     int failure_count = 0;
  9224.     WIN32_FIND_DATA current_file;
  9225.     HANDLE file_search = FindFirstFile(file_pattern, ¤t_file);
  9226.  
  9227.     if (file_search != INVALID_HANDLE_VALUE)
  9228.     {
  9229.         do
  9230.         {
  9231.             // Since other script threads can interrupt during LONG_OPERATION_UPDATE, it's important that
  9232.             // this command not refer to sArgDeref[] and sArgVar[] anytime after an interruption becomes
  9233.             // possible. This is because an interrupting thread usually changes the values to something
  9234.             // inappropriate for this thread.
  9235.             LONG_OPERATION_UPDATE
  9236.  
  9237.             if (current_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  9238.             {
  9239.                 if (current_file.cFileName[0] == '.' && (!current_file.cFileName[1]    // Relies on short-circuit boolean order.
  9240.                     || current_file.cFileName[1] == '.' && !current_file.cFileName[2]) //
  9241.                     // Regardless of whether this folder will be recursed into, this folder's own attributes
  9242.                     // will not be affected when the mode is files-only:
  9243.                     || aOperateOnFolders == FILE_LOOP_FILES_ONLY)
  9244.                     continue; // Never operate upon or recurse into these.
  9245.             }
  9246.             else // It's a file, not a folder.
  9247.                 if (aOperateOnFolders == FILE_LOOP_FOLDERS_ONLY)
  9248.                     continue;
  9249.  
  9250.             if (strlen(current_file.cFileName) > space_remaining)
  9251.             {
  9252.                 // v1.0.45.03: Don't even try to operate upon truncated filenames in case they accidentally
  9253.                 // match the name of a real/existing file.
  9254.                 ++failure_count;
  9255.                 continue;
  9256.             }
  9257.             // Otherwise, make file_path be the filespec of the file to operate upon:
  9258.             strcpy(append_pos, current_file.cFileName); // Above has ensured this won't overflow.
  9259.  
  9260.             for (cp = attributes; *cp; ++cp)
  9261.             {
  9262.                 switch (toupper(*cp))
  9263.                 {
  9264.                 case '+': mode = ATTRIB_MODE_ADD; break;
  9265.                 case '-': mode = ATTRIB_MODE_REMOVE; break;
  9266.                 case '^': mode = ATTRIB_MODE_TOGGLE; break;
  9267.                 // Note that D (directory) and C (compressed) are currently not supported:
  9268.                 case 'R':
  9269.                     if (mode == ATTRIB_MODE_ADD)
  9270.                         current_file.dwFileAttributes |= FILE_ATTRIBUTE_READONLY;
  9271.                     else if (mode == ATTRIB_MODE_REMOVE)
  9272.                         current_file.dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY;
  9273.                     else if (mode == ATTRIB_MODE_TOGGLE)
  9274.                         current_file.dwFileAttributes ^= FILE_ATTRIBUTE_READONLY;
  9275.                     break;
  9276.                 case 'A':
  9277.                     if (mode == ATTRIB_MODE_ADD)
  9278.                         current_file.dwFileAttributes |= FILE_ATTRIBUTE_ARCHIVE;
  9279.                     else if (mode == ATTRIB_MODE_REMOVE)
  9280.                         current_file.dwFileAttributes &= ~FILE_ATTRIBUTE_ARCHIVE;
  9281.                     else if (mode == ATTRIB_MODE_TOGGLE)
  9282.                         current_file.dwFileAttributes ^= FILE_ATTRIBUTE_ARCHIVE;
  9283.                     break;
  9284.                 case 'S':
  9285.                     if (mode == ATTRIB_MODE_ADD)
  9286.                         current_file.dwFileAttributes |= FILE_ATTRIBUTE_SYSTEM;
  9287.                     else if (mode == ATTRIB_MODE_REMOVE)
  9288.                         current_file.dwFileAttributes &= ~FILE_ATTRIBUTE_SYSTEM;
  9289.                     else if (mode == ATTRIB_MODE_TOGGLE)
  9290.                         current_file.dwFileAttributes ^= FILE_ATTRIBUTE_SYSTEM;
  9291.                     break;
  9292.                 case 'H':
  9293.                     if (mode == ATTRIB_MODE_ADD)
  9294.                         current_file.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
  9295.                     else if (mode == ATTRIB_MODE_REMOVE)
  9296.                         current_file.dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
  9297.                     else if (mode == ATTRIB_MODE_TOGGLE)
  9298.                         current_file.dwFileAttributes ^= FILE_ATTRIBUTE_HIDDEN;
  9299.                     break;
  9300.                 case 'N':  // Docs say it's valid only when used alone.  But let the API handle it if this is not so.
  9301.                     if (mode == ATTRIB_MODE_ADD)
  9302.                         current_file.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
  9303.                     else if (mode == ATTRIB_MODE_REMOVE)
  9304.                         current_file.dwFileAttributes &= ~FILE_ATTRIBUTE_NORMAL;
  9305.                     else if (mode == ATTRIB_MODE_TOGGLE)
  9306.                         current_file.dwFileAttributes ^= FILE_ATTRIBUTE_NORMAL;
  9307.                     break;
  9308.                 case 'O':
  9309.                     if (mode == ATTRIB_MODE_ADD)
  9310.                         current_file.dwFileAttributes |= FILE_ATTRIBUTE_OFFLINE;
  9311.                     else if (mode == ATTRIB_MODE_REMOVE)
  9312.                         current_file.dwFileAttributes &= ~FILE_ATTRIBUTE_OFFLINE;
  9313.                     else if (mode == ATTRIB_MODE_TOGGLE)
  9314.                         current_file.dwFileAttributes ^= FILE_ATTRIBUTE_OFFLINE;
  9315.                     break;
  9316.                 case 'T':
  9317.                     if (mode == ATTRIB_MODE_ADD)
  9318.                         current_file.dwFileAttributes |= FILE_ATTRIBUTE_TEMPORARY;
  9319.                     else if (mode == ATTRIB_MODE_REMOVE)
  9320.                         current_file.dwFileAttributes &= ~FILE_ATTRIBUTE_TEMPORARY;
  9321.                     else if (mode == ATTRIB_MODE_TOGGLE)
  9322.                         current_file.dwFileAttributes ^= FILE_ATTRIBUTE_TEMPORARY;
  9323.                     break;
  9324.                 }
  9325.             }
  9326.  
  9327.             if (!SetFileAttributes(file_path, current_file.dwFileAttributes))
  9328.                 ++failure_count;
  9329.         } while (FindNextFile(file_search, ¤t_file));
  9330.  
  9331.         FindClose(file_search);
  9332.     } // if (file_search != INVALID_HANDLE_VALUE)
  9333.  
  9334.     if (aDoRecurse && space_remaining > 2) // The space_remaining check ensures there's enough room to append "*.*" (if not, just avoid recursing into it due to rarity).
  9335.     {
  9336.         // Testing shows that the ANSI version of FindFirstFile() will not accept a path+pattern longer
  9337.         // than 256 or so, even if the pattern would match files whose names are short enough to be legal.
  9338.         // Therefore, as of v1.0.25, there is also a hard limit of MAX_PATH on all these variables.
  9339.         // MSDN confirms this in a vague way: "In the ANSI version of FindFirstFile(), [plpFileName] is
  9340.         // limited to MAX_PATH characters."
  9341.         strcpy(append_pos, "*.*"); // Above has ensured this won't overflow.
  9342.         file_search = FindFirstFile(file_path, ¤t_file);
  9343.  
  9344.         if (file_search != INVALID_HANDLE_VALUE)
  9345.         {
  9346.             size_t pattern_length = strlen(naked_filename_or_pattern);
  9347.             do
  9348.             {
  9349.                 LONG_OPERATION_UPDATE
  9350.                 if (!(current_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  9351.                     || current_file.cFileName[0] == '.' && (!current_file.cFileName[1]     // Relies on short-circuit boolean order.
  9352.                         || current_file.cFileName[1] == '.' && !current_file.cFileName[2]) //
  9353.                     // v1.0.45.03: Skip over folders whose full-path-names are too long to be supported by the ANSI
  9354.                     // versions of FindFirst/FindNext.  Without this fix, it might be possible for infinite recursion
  9355.                     // to occur (see PerformLoop() for more comments).
  9356.                     || pattern_length + strlen(current_file.cFileName) >= space_remaining) // >= vs. > to reserve 1 for the backslash to be added between cFileName and naked_filename_or_pattern.
  9357.                     continue; // Never recurse into these.
  9358.                 // This will build the string CurrentDir+SubDir+FilePatternOrName.
  9359.                 // If FilePatternOrName doesn't contain a wildcard, the recursion
  9360.                 // process will attempt to operate on the originally-specified
  9361.                 // single filename or folder name if it occurs anywhere else in the
  9362.                 // tree, e.g. recursing C:\Temp\temp.txt would affect all occurences
  9363.                 // of temp.txt both in C:\Temp and any subdirectories it might contain:
  9364.                 sprintf(append_pos, "%s\\%s" // Above has ensured this won't overflow.
  9365.                     , current_file.cFileName, naked_filename_or_pattern);
  9366.                 failure_count += FileSetAttrib(attributes, file_path, aOperateOnFolders, aDoRecurse, true);
  9367.             } while (FindNextFile(file_search, ¤t_file));
  9368.             FindClose(file_search);
  9369.         } // if (file_search != INVALID_HANDLE_VALUE)
  9370.     } // if (aDoRecurse)
  9371.  
  9372.     if (!aCalledRecursively) // i.e. Only need to do this if we're returning to top-level caller:
  9373.         g_ErrorLevel->Assign(failure_count); // i.e. indicate success if there were no failures.
  9374.     return failure_count;
  9375. }
  9376.  
  9377.  
  9378.  
  9379. ResultType Line::FileGetTime(char *aFilespec, char aWhichTime)
  9380. {
  9381.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default.
  9382.     OUTPUT_VAR->Assign(); // Init to be blank, in case of failure.
  9383.  
  9384.     if (!aFilespec || !*aFilespec)
  9385.         return OK;  // Let ErrorLevel indicate an error, since this is probably not what the user intended.
  9386.  
  9387.     // Don't use CreateFile() & FileGetSize() size they will fail to work on a file that's in use.
  9388.     // Research indicates that this method has no disadvantages compared to the other method.
  9389.     WIN32_FIND_DATA found_file;
  9390.     HANDLE file_search = FindFirstFile(aFilespec, &found_file);
  9391.     if (file_search == INVALID_HANDLE_VALUE)
  9392.         return OK;  // Let ErrorLevel Tell the story.
  9393.     FindClose(file_search);
  9394.  
  9395.     FILETIME local_file_time;
  9396.     switch (toupper(aWhichTime))
  9397.     {
  9398.     case 'C': // File's creation time.
  9399.         FileTimeToLocalFileTime(&found_file.ftCreationTime, &local_file_time);
  9400.         break;
  9401.     case 'A': // File's last access time.
  9402.         FileTimeToLocalFileTime(&found_file.ftLastAccessTime, &local_file_time);
  9403.         break;
  9404.     default:  // 'M', unspecified, or some other value.  Use the file's modification time.
  9405.         FileTimeToLocalFileTime(&found_file.ftLastWriteTime, &local_file_time);
  9406.     }
  9407.  
  9408.     g_ErrorLevel->Assign(ERRORLEVEL_NONE);  // Indicate success.
  9409.     char local_file_time_string[128];
  9410.     return OUTPUT_VAR->Assign(FileTimeToYYYYMMDD(local_file_time_string, local_file_time));
  9411. }
  9412.  
  9413.  
  9414.  
  9415. int Line::FileSetTime(char *aYYYYMMDD, char *aFilePattern, char aWhichTime
  9416.     , FileLoopModeType aOperateOnFolders, bool aDoRecurse, bool aCalledRecursively)
  9417. // Returns the number of files and folders that could not be changed due to an error.
  9418. // Current limitation: It will not recurse into subfolders unless their names also match
  9419. // aFilePattern.
  9420. {
  9421.     if (!aCalledRecursively)  // i.e. Only need to do this if we're not called by ourself:
  9422.     {
  9423.         g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default
  9424.         if (!*aFilePattern)
  9425.             return 0;  // Let ErrorLevel indicate an error, since this is probably not what the user intended.
  9426.         if (aOperateOnFolders == FILE_LOOP_INVALID) // In case runtime dereference of a var was an invalid value.
  9427.             aOperateOnFolders = FILE_LOOP_FILES_ONLY;  // Set default.
  9428.     }
  9429.  
  9430.     if (strlen(aFilePattern) >= MAX_PATH) // Checked early to simplify other things below.
  9431.         return 0; // Let the above ErrorLevel indicate the problem.
  9432.  
  9433.     // Related to the comment at the top: Since the script subroutine that resulted in the call to
  9434.     // this function can be interrupted during our MsgSleep(), make a copy of any params that might
  9435.     // currently point directly to the deref buffer.  This is done because their contents might
  9436.     // be overwritten by the interrupting subroutine:
  9437.     char yyyymmdd[64]; // Even do this one since its value is passed recursively in calls to self.
  9438.     strlcpy(yyyymmdd, aYYYYMMDD, sizeof(yyyymmdd));
  9439.     char file_pattern[MAX_PATH];
  9440.     strcpy(file_pattern, aFilePattern); // An earlier check has ensured this won't overflow.
  9441.  
  9442.     FILETIME ft, ftUTC;
  9443.     if (*yyyymmdd)
  9444.     {
  9445.         // Convert the arg into the time struct as local (non-UTC) time:
  9446.         if (!YYYYMMDDToFileTime(yyyymmdd, ft))
  9447.             return 0;  // Let ErrorLevel tell the story.
  9448.         // Convert from local to UTC:
  9449.         if (!LocalFileTimeToFileTime(&ft, &ftUTC))
  9450.             return 0;  // Let ErrorLevel tell the story.
  9451.     }
  9452.     else // User wants to use the current time (i.e. now) as the new timestamp.
  9453.         GetSystemTimeAsFileTime(&ftUTC);
  9454.  
  9455.     // This following section is very similar to that in FileSetAttrib and FileDelete:
  9456.     char file_path[MAX_PATH]; // Giving +3 extra for "*.*" seems fairly pointless because any files that actually need that extra room would fail to be retrieved by FindFirst/Next due to their inability to support paths much over 256.
  9457.     strcpy(file_path, aFilePattern); // An earlier check has ensured this won't overflow.
  9458.  
  9459.     size_t file_path_length; // The length of just the path portion of the filespec.
  9460.     char *last_backslash = strrchr(file_path, '\\');
  9461.     if (last_backslash)
  9462.     {
  9463.         // Remove the filename and/or wildcard part.   But leave the trailing backslash on it for
  9464.         // consistency with below:
  9465.         *(last_backslash + 1) = '\0';
  9466.         file_path_length = strlen(file_path);
  9467.     }
  9468.     else // Use current working directory, e.g. if user specified only *.*
  9469.     {
  9470.         *file_path = '\0';
  9471.         file_path_length = 0;
  9472.     }
  9473.     char *append_pos = file_path + file_path_length; // For performance, copy in the unchanging part only once.  This is where the changing part gets appended.
  9474.     size_t space_remaining = sizeof(file_path) - file_path_length - 1; // Space left in file_path for the changing part.
  9475.  
  9476.     // For use with aDoRecurse, get just the naked file name/pattern:
  9477.     char *naked_filename_or_pattern = strrchr(file_pattern, '\\');
  9478.     if (naked_filename_or_pattern)
  9479.         ++naked_filename_or_pattern;
  9480.     else
  9481.         naked_filename_or_pattern = file_pattern;
  9482.  
  9483.     if (!StrChrAny(naked_filename_or_pattern, "?*"))
  9484.         // Since no wildcards, always operate on this single item even if it's a folder.
  9485.         aOperateOnFolders = FILE_LOOP_FILES_AND_FOLDERS;
  9486.  
  9487.     HANDLE hFile;
  9488.     LONG_OPERATION_INIT
  9489.     int failure_count = 0;
  9490.     WIN32_FIND_DATA current_file;
  9491.     HANDLE file_search = FindFirstFile(file_pattern, ¤t_file);
  9492.  
  9493.     if (file_search != INVALID_HANDLE_VALUE)
  9494.     {
  9495.         do
  9496.         {
  9497.             // Since other script threads can interrupt during LONG_OPERATION_UPDATE, it's important that
  9498.             // this command not refer to sArgDeref[] and sArgVar[] anytime after an interruption becomes
  9499.             // possible. This is because an interrupting thread usually changes the values to something
  9500.             // inappropriate for this thread.
  9501.             LONG_OPERATION_UPDATE
  9502.  
  9503.             if (current_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  9504.             {
  9505.                 if (current_file.cFileName[0] == '.' && (!current_file.cFileName[1]    // Relies on short-circuit boolean order.
  9506.                     || current_file.cFileName[1] == '.' && !current_file.cFileName[2]) //
  9507.                     // Regardless of whether this folder will be recursed into, this folder's own timestamp
  9508.                     // will not be affected when the mode is files-only:
  9509.                     || aOperateOnFolders == FILE_LOOP_FILES_ONLY)
  9510.                     continue; // Never operate upon or recurse into these.
  9511.             }
  9512.             else // It's a file, not a folder.
  9513.                 if (aOperateOnFolders == FILE_LOOP_FOLDERS_ONLY)
  9514.                     continue;
  9515.  
  9516.             if (strlen(current_file.cFileName) > space_remaining)
  9517.             {
  9518.                 // v1.0.45.03: Don't even try to operate upon truncated filenames in case they accidentally
  9519.                 // match the name of a real/existing file.
  9520.                 ++failure_count;
  9521.                 continue;
  9522.             }
  9523.             // Otherwise, make file_path be the filespec of the file to operate upon:
  9524.             strcpy(append_pos, current_file.cFileName); // Above has ensured this won't overflow.
  9525.  
  9526.             // Open existing file.  Uses CreateFile() rather than OpenFile for an expectation
  9527.             // of greater compatibility for all files, and folder support too.
  9528.             // FILE_FLAG_NO_BUFFERING might improve performance because all we're doing is
  9529.             // changing one of the file's attributes.  FILE_FLAG_BACKUP_SEMANTICS must be
  9530.             // used, otherwise changing the time of a directory under NT and beyond will
  9531.             // not succeed.  Win95 (not sure about Win98/Me) does not support this, but it
  9532.             // should be harmless to specify it even if the OS is Win95:
  9533.             hFile = CreateFile(file_path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE
  9534.                 , (LPSECURITY_ATTRIBUTES)NULL, OPEN_EXISTING
  9535.                 , FILE_FLAG_NO_BUFFERING | FILE_FLAG_BACKUP_SEMANTICS, NULL);
  9536.             if (hFile == INVALID_HANDLE_VALUE)
  9537.             {
  9538.                 ++failure_count;
  9539.                 continue;
  9540.             }
  9541.  
  9542.             switch (toupper(aWhichTime))
  9543.             {
  9544.             case 'C': // File's creation time.
  9545.                 if (!SetFileTime(hFile, &ftUTC, NULL, NULL))
  9546.                     ++failure_count;
  9547.                 break;
  9548.             case 'A': // File's last access time.
  9549.                 if (!SetFileTime(hFile, NULL, &ftUTC, NULL))
  9550.                     ++failure_count;
  9551.                 break;
  9552.             default:  // 'M', unspecified, or some other value.  Use the file's modification time.
  9553.                 if (!SetFileTime(hFile, NULL, NULL, &ftUTC))
  9554.                     ++failure_count;
  9555.             }
  9556.  
  9557.             CloseHandle(hFile);
  9558.         } while (FindNextFile(file_search, ¤t_file));
  9559.  
  9560.         FindClose(file_search);
  9561.     } // if (file_search != INVALID_HANDLE_VALUE)
  9562.  
  9563.     // This section is identical to that in FileSetAttrib() except for the recursive function
  9564.     // call itself, so see comments there for details:
  9565.     if (aDoRecurse && space_remaining > 2) // The space_remaining check ensures there's enough room to append "*.*" (if not, just avoid recursing into it due to rarity).
  9566.     {
  9567.         // Testing shows that the ANSI version of FindFirstFile() will not accept a path+pattern longer
  9568.         // than 256 or so, even if the pattern would match files whose names are short enough to be legal.
  9569.         // Therefore, as of v1.0.25, there is also a hard limit of MAX_PATH on all these variables.
  9570.         // MSDN confirms this in a vague way: "In the ANSI version of FindFirstFile(), [plpFileName] is
  9571.         // limited to MAX_PATH characters."
  9572.         strcpy(append_pos, "*.*"); // Above has ensured this won't overflow.
  9573.         file_search = FindFirstFile(file_path, ¤t_file);
  9574.  
  9575.         if (file_search != INVALID_HANDLE_VALUE)
  9576.         {
  9577.             size_t pattern_length = strlen(naked_filename_or_pattern);
  9578.             do
  9579.             {
  9580.                 LONG_OPERATION_UPDATE
  9581.                 if (!(current_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  9582.                     || current_file.cFileName[0] == '.' && (!current_file.cFileName[1]     // Relies on short-circuit boolean order.
  9583.                         || current_file.cFileName[1] == '.' && !current_file.cFileName[2]) //
  9584.                     // v1.0.45.03: Skip over folders whose full-path-names are too long to be supported by the ANSI
  9585.                     // versions of FindFirst/FindNext.  Without this fix, it might be possible for infinite recursion
  9586.                     // to occur (see PerformLoop() for more comments).
  9587.                     || pattern_length + strlen(current_file.cFileName) >= space_remaining) // >= vs. > to reserve 1 for the backslash to be added between cFileName and naked_filename_or_pattern.
  9588.                     continue;
  9589.                 sprintf(append_pos, "%s\\%s" // Above has ensured this won't overflow.
  9590.                     , current_file.cFileName, naked_filename_or_pattern);
  9591.                 failure_count += FileSetTime(yyyymmdd, file_path, aWhichTime, aOperateOnFolders, aDoRecurse, true);
  9592.             } while (FindNextFile(file_search, ¤t_file));
  9593.             FindClose(file_search);
  9594.         } // if (file_search != INVALID_HANDLE_VALUE)
  9595.     } // if (aDoRecurse)
  9596.  
  9597.     if (!aCalledRecursively) // i.e. Only need to do this if we're returning to top-level caller:
  9598.         g_ErrorLevel->Assign(failure_count); // i.e. indicate success if there were no failures.
  9599.     return failure_count;
  9600. }
  9601.  
  9602.  
  9603.  
  9604. ResultType Line::FileGetSize(char *aFilespec, char *aGranularity)
  9605. {
  9606.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default
  9607.     OUTPUT_VAR->Assign(); // Init to be blank, in case of failure.
  9608.  
  9609.     if (!aFilespec || !*aFilespec)
  9610.         return OK;  // Let ErrorLevel indicate an error, since this is probably not what the user intended.
  9611.  
  9612.     // Don't use CreateFile() & FileGetSize() size they will fail to work on a file that's in use.
  9613.     // Research indicates that this method has no disadvantages compared to the other method.
  9614.     WIN32_FIND_DATA found_file;
  9615.     HANDLE file_search = FindFirstFile(aFilespec, &found_file);
  9616.     if (file_search == INVALID_HANDLE_VALUE)
  9617.         return OK;  // Let ErrorLevel Tell the story.
  9618.     FindClose(file_search);
  9619.  
  9620.     unsigned __int64 size = (found_file.nFileSizeHigh * (unsigned __int64)MAXDWORD) + found_file.nFileSizeLow;
  9621.  
  9622.     switch(toupper(*aGranularity))
  9623.     {
  9624.     case 'K': // KB
  9625.         size /= 1024;
  9626.         break;
  9627.     case 'M': // MB
  9628.         size /= (1024 * 1024);
  9629.         break;
  9630.     // default: // i.e. either 'B' for bytes, or blank, or some other unknown value, so default to bytes.
  9631.         // do nothing
  9632.     }
  9633.  
  9634.     g_ErrorLevel->Assign(ERRORLEVEL_NONE);  // Indicate success.
  9635.     return OUTPUT_VAR->Assign((__int64)(size > ULLONG_MAX ? -1 : size)); // i.e. don't allow it to wrap around.
  9636.     // The below comment is obsolete in light of the switch to 64-bit integers.  But it might
  9637.     // be good to keep for background:
  9638.     // Currently, the above is basically subject to a 2 gig limit, I believe, after which the
  9639.     // size will appear to be negative.  Beyond a 4 gig limit, the value will probably wrap around
  9640.     // to zero and start counting from there as file sizes grow beyond 4 gig (UPDATE: The size
  9641.     // is now set to -1 [the maximum DWORD when expressed as a signed int] whenever >4 gig).
  9642.     // There's not much sense in putting values larger than 2 gig into the var as a text string
  9643.     // containing a positive number because such a var could never be properly handled by anything
  9644.     // that compares to it (e.g. IfGreater) or does math on it (e.g. EnvAdd), since those operations
  9645.     // use ATOI() to convert the string.  So as a future enhancement (unless the whole program is
  9646.     // revamped to use 64bit ints or something) might add an optional param to the end to indicate
  9647.     // size should be returned in K(ilobyte) or M(egabyte).  However, this is sorta bad too since
  9648.     // adding a param can break existing scripts which use filenames containing commas (delimiters)
  9649.     // with this command.  Therefore, I think I'll just add the K/M param now.
  9650.     // Also, the above assigns an int because unsigned ints should never be stored in script
  9651.     // variables.  This is because an unsigned variable larger than INT_MAX would not be properly
  9652.     // converted by ATOI(), which is current standard method for variables to be auto-converted
  9653.     // from text back to a number whenever that is needed.
  9654. }
  9655.  
  9656.  
  9657.  
  9658. ResultType Line::SetToggleState(vk_type aVK, ToggleValueType &ForceLock, char *aToggleText)
  9659. // Caller must have already validated that the args are correct.
  9660. // Always returns OK, for use as caller's return value.
  9661. {
  9662.     ToggleValueType toggle = ConvertOnOffAlways(aToggleText, NEUTRAL);
  9663.     switch (toggle)
  9664.     {
  9665.     case TOGGLED_ON:
  9666.     case TOGGLED_OFF:
  9667.         // Turning it on or off overrides any prior AlwaysOn or AlwaysOff setting.
  9668.         // Probably need to change the setting BEFORE attempting to toggle the
  9669.         // key state, otherwise the hook may prevent the state from being changed
  9670.         // if it was set to be AlwaysOn or AlwaysOff:
  9671.         ForceLock = NEUTRAL;
  9672.         ToggleKeyState(aVK, toggle);
  9673.         break;
  9674.     case ALWAYS_ON:
  9675.     case ALWAYS_OFF:
  9676.         ForceLock = (toggle == ALWAYS_ON) ? TOGGLED_ON : TOGGLED_OFF; // Must do this first.
  9677.         ToggleKeyState(aVK, ForceLock);
  9678.         // The hook is currently needed to support keeping these keys AlwaysOn or AlwaysOff, though
  9679.         // there may be better ways to do it (such as registering them as a hotkey, but
  9680.         // that may introduce quite a bit of complexity):
  9681.         Hotkey::InstallKeybdHook();
  9682.         break;
  9683.     case NEUTRAL:
  9684.         // Note: No attempt is made to detect whether the keybd hook should be deinstalled
  9685.         // because it's no longer needed due to this change.  That would require some 
  9686.         // careful thought about the impact on the status variables in the Hotkey class, etc.,
  9687.         // so it can be left for a future enhancement:
  9688.         ForceLock = NEUTRAL;
  9689.         break;
  9690.     }
  9691.     return OK;
  9692. }
  9693.  
  9694.  
  9695.  
  9696. ////////////////////////////////
  9697. // Misc lower level functions //
  9698. ////////////////////////////////
  9699.  
  9700. HWND Line::DetermineTargetWindow(char *aTitle, char *aText, char *aExcludeTitle, char *aExcludeText)
  9701. {
  9702.     HWND target_window; // A variable of this name is used by the macros below.
  9703.     IF_USE_FOREGROUND_WINDOW(g.DetectHiddenWindows, aTitle, aText, aExcludeTitle, aExcludeText)
  9704.     else if (*aTitle || *aText || *aExcludeTitle || *aExcludeText)
  9705.         target_window = WinExist(g, aTitle, aText, aExcludeTitle, aExcludeText);
  9706.     else // Use the "last found" window.
  9707.         target_window = GetValidLastUsedWindow(g);
  9708.     return target_window;
  9709. }
  9710.  
  9711.  
  9712.  
  9713. #ifndef AUTOHOTKEYSC
  9714. int Line::ConvertEscapeChar(char *aFilespec)
  9715. {
  9716.     bool aFromAutoIt2 = true;  // This function currnetly always uses these defaults, so they're no longer passed in.
  9717.     char aOldChar = '\\';      //
  9718.     char aNewChar = '`';       //
  9719.     // Commented out since currently no need:
  9720.     //if (aOldChar == aNewChar)
  9721.     //{
  9722.     //    MsgBox("Conversion: The OldChar must not be the same as the NewChar.");
  9723.     //    return 1;
  9724.     //}
  9725.  
  9726.     FILE *f1, *f2;
  9727.     if (   !(f1 = fopen(aFilespec, "r"))   )
  9728.     {
  9729.         MsgBox(aFilespec, 0, "Can't open file:"); // Short msg, and same as one below, since rare.
  9730.         return 1; // Failure.
  9731.     }
  9732.  
  9733.     char new_filespec[MAX_PATH + 10];  // +10 in case StrReplace below would otherwise overflow the buffer.
  9734.     strlcpy(new_filespec, aFilespec, sizeof(new_filespec));
  9735.     StrReplace(new_filespec, CONVERSION_FLAG, "-NEW" EXT_AUTOHOTKEY, SCS_INSENSITIVE);
  9736.  
  9737.     if (   !(f2 = fopen(new_filespec, "w"))   )
  9738.     {
  9739.         fclose(f1);
  9740.         MsgBox(new_filespec, 0, "Can't open file:"); // Short msg, and same as previous, since rare.
  9741.         return 1; // Failure
  9742.     }
  9743.  
  9744.     char buf[LINE_SIZE], *cp, next_char;
  9745.     size_t line_count, buf_length;
  9746.  
  9747.     for (line_count = 0;;)
  9748.     {
  9749.         if (   -1 == (buf_length = ConvertEscapeCharGetLine(buf, (int)(sizeof(buf) - 1), f1))   )
  9750.             break;
  9751.         ++line_count;
  9752.  
  9753.         for (cp = buf; ; ++cp)  // Increment to skip over the symbol just found by the inner for().
  9754.         {
  9755.             for (; *cp && *cp != aOldChar && *cp != aNewChar; ++cp);  // Find the next escape char.
  9756.  
  9757.             if (!*cp) // end of string.
  9758.                 break;
  9759.  
  9760.             if (*cp == aNewChar)
  9761.             {
  9762.                 if (buf_length < sizeof(buf) - 1)  // buffer safety check
  9763.                 {
  9764.                     // Insert another of the same char to make it a pair, so that it becomes the
  9765.                     // literal version of this new escape char (e.g. ` --> ``)
  9766.                     MoveMemory(cp + 1, cp, strlen(cp) + 1);
  9767.                     *cp = aNewChar;
  9768.                     // Increment so that the loop will resume checking at the char after this new pair.
  9769.                     // Example: `` becomes ````
  9770.                     ++cp;  // Only +1 because the outer for-loop will do another increment.
  9771.                     ++buf_length;
  9772.                 }
  9773.                 continue;
  9774.             }
  9775.  
  9776.             // Otherwise *cp == aOldChar:
  9777.             next_char = cp[1];
  9778.             if (next_char == aOldChar)
  9779.             {
  9780.                 // This is a double-escape (e.g. \\ in AutoIt2).  Replace it with a single
  9781.                 // character of the same type:
  9782.                 MoveMemory(cp, cp + 1, strlen(cp + 1) + 1);
  9783.                 --buf_length;
  9784.             }
  9785.             else
  9786.                 // It's just a normal escape sequence.  Even if it's not a valid escape sequence,
  9787.                 // convert it anyway because it's more failsafe to do so (the script parser will
  9788.                 // handle such things much better than we can when the script is run):
  9789.                 *cp = aNewChar;
  9790.         }
  9791.         // Before the line is written, also do some conversions if the source file is known to
  9792.         // be an AutoIt2 script:
  9793.         if (aFromAutoIt2)
  9794.         {
  9795.             // This will not fix all possible uses of A_ScriptDir, just those that are dereferences.
  9796.             // For example, this would not be fixed: StringLen, length, a_ScriptDir
  9797.             StrReplace(buf, "%A_ScriptDir%", "%A_ScriptDir%\\", SCS_INSENSITIVE, UINT_MAX, sizeof(buf));
  9798.             // Later can add some other, similar conversions here.
  9799.         }
  9800.         fputs(buf, f2);
  9801.     }
  9802.  
  9803.     fclose(f1);
  9804.     fclose(f2);
  9805.     MsgBox("The file was successfully converted.");
  9806.     return 0;  // Return 0 on success in this case.
  9807. }
  9808.  
  9809.  
  9810.  
  9811. size_t Line::ConvertEscapeCharGetLine(char *aBuf, int aMaxCharsToRead, FILE *fp)
  9812. {
  9813.     if (!aBuf || !fp) return -1;
  9814.     if (aMaxCharsToRead < 1) return 0;
  9815.     if (feof(fp)) return -1; // Previous call to this function probably already read the last line.
  9816.     if (fgets(aBuf, aMaxCharsToRead, fp) == NULL) // end-of-file or error
  9817.     {
  9818.         *aBuf = '\0';  // Reset since on error, contents added by fgets() are indeterminate.
  9819.         return -1;
  9820.     }
  9821.     return strlen(aBuf);
  9822. }
  9823. #endif  // The functions above are not needed by the self-contained version.
  9824.  
  9825.  
  9826.  
  9827. bool Line::FileIsFilteredOut(WIN32_FIND_DATA &aCurrentFile, FileLoopModeType aFileLoopMode
  9828.     , char *aFilePath, size_t aFilePathLength)
  9829. // Caller has ensured that aFilePath (if non-blank) has a trailing backslash.
  9830. {
  9831.     if (aCurrentFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) // It's a folder.
  9832.     {
  9833.         if (aFileLoopMode == FILE_LOOP_FILES_ONLY
  9834.             || aCurrentFile.cFileName[0] == '.' && (!aCurrentFile.cFileName[1]      // Relies on short-circuit boolean order.
  9835.                 || aCurrentFile.cFileName[1] == '.' && !aCurrentFile.cFileName[2])) //
  9836.             return true; // Exclude this folder by returning true.
  9837.     }
  9838.     else // it's not a folder.
  9839.         if (aFileLoopMode == FILE_LOOP_FOLDERS_ONLY)
  9840.             return true; // Exclude this file by returning true.
  9841.  
  9842.     // Since file was found, also prepend the file's path to its name for the caller:
  9843.     if (*aFilePath)
  9844.     {
  9845.         // Seems best to check length in advance because it allows a faster move/copy method further below
  9846.         // (in lieu of snprintf(), which is probably quite a bit slower than the method here).
  9847.         size_t name_length = strlen(aCurrentFile.cFileName);
  9848.         if (aFilePathLength + name_length >= MAX_PATH)
  9849.             // v1.0.45.03: Filter out filenames that would be truncated because it seems undesirable in 99% of
  9850.             // cases to include such "faulty" data in the loop.  Most scripts would want to skip them rather than
  9851.             // seeing the truncated names.  Furthermore, a truncated name might accidentally match the name
  9852.             // of a legitimate non-trucated filename, which could cause such a name to get retrieved twice by
  9853.             // the loop (or other undesirable side-effects).
  9854.             return true;
  9855.         //else no overflow is possible, so below can move things around inside the buffer without concern.
  9856.         memmove(aCurrentFile.cFileName + aFilePathLength, aCurrentFile.cFileName, name_length + 1); // memmove() because source & dest might overlap.  +1 to include the terminator.
  9857.         memcpy(aCurrentFile.cFileName, aFilePath, aFilePathLength); // Prepend in the area liberated by the above. Don't include the terminator since this is a concat operation.
  9858.     }
  9859.     return false; // Indicate that this file is not to be filtered out.
  9860. }
  9861.  
  9862.  
  9863.  
  9864. Label *Line::GetJumpTarget(bool aIsDereferenced)
  9865. {
  9866.     char *target_label = aIsDereferenced ? ARG1 : RAW_ARG1;
  9867.     Label *label = g_script.FindLabel(target_label);
  9868.     if (!label)
  9869.     {
  9870.         if (aIsDereferenced)
  9871.             LineError(ERR_NO_LABEL ERR_ABORT, FAIL, target_label);
  9872.         else
  9873.             LineError(ERR_NO_LABEL, FAIL, target_label);
  9874.         return NULL;
  9875.     }
  9876.     if (!aIsDereferenced)
  9877.         mRelatedLine = (Line *)label; // The script loader has ensured that label->mJumpToLine isn't NULL.
  9878.     // else don't update it, because that would permanently resolve the jump target, and we want it to stay dynamic.
  9879.     // Seems best to do this even for GOSUBs even though it's a bit weird:
  9880.     return IsJumpValid(*label);
  9881.     // Any error msg was already displayed by the above call.
  9882. }
  9883.  
  9884.  
  9885.  
  9886. Label *Line::IsJumpValid(Label &aTargetLabel)
  9887. // Returns aTargetLabel is the jump is valid, or NULL otherwise.
  9888. {
  9889.     // aTargetLabel can be NULL if this Goto's target is the physical end of the script.
  9890.     // And such a destination is always valid, regardless of where aOrigin is.
  9891.     // UPDATE: It's no longer possible for the destination of a Goto or Gosub to be
  9892.     // NULL because the script loader has ensured that the end of the script always has
  9893.     // an extra ACT_EXIT that serves as an anchor for any final labels in the script:
  9894.     //if (aTargetLabel == NULL)
  9895.     //    return OK;
  9896.     // The above check is also necessary to avoid dereferencing a NULL pointer below.
  9897.  
  9898.     Line *parent_line_of_label_line;
  9899.     if (   !(parent_line_of_label_line = aTargetLabel.mJumpToLine->mParentLine)   )
  9900.         // A Goto/Gosub can always jump to a point anywhere in the outermost layer
  9901.         // (i.e. outside all blocks) without restriction:
  9902.         return &aTargetLabel; // Indicate success.
  9903.  
  9904.     // So now we know this Goto/Gosub is attempting to jump into a block somewhere.  Is that
  9905.     // block a legal place to jump?:
  9906.  
  9907.     for (Line *ancestor = mParentLine; ancestor != NULL; ancestor = ancestor->mParentLine)
  9908.         if (parent_line_of_label_line == ancestor)
  9909.             // Since aTargetLabel is in the same block as the Goto line itself (or a block
  9910.             // that encloses that block), it's allowed:
  9911.             return &aTargetLabel; // Indicate success.
  9912.     // This can happen if the Goto's target is at a deeper level than it, or if the target
  9913.     // is at a more shallow level but is in some block totally unrelated to it!
  9914.     // Returns FAIL by default, which is what we want because that value is zero:
  9915.     LineError("A Goto/Gosub must not jump into a block that doesn't enclose it."); // Omit GroupActivate from the error msg since that is rare enough to justify the increase in common-case clarify.
  9916.     return NULL;
  9917.     // Above currently doesn't attempt to detect runtime vs. load-time for the purpose of appending
  9918.     // ERR_ABORT.
  9919. }
  9920.  
  9921.  
  9922.  
  9923. ////////////////////////
  9924. // BUILT-IN VARIABLES //
  9925. ////////////////////////
  9926.  
  9927. VarSizeType BIV_True_False(char *aBuf, char *aVarName)
  9928. {
  9929.     if (aBuf)
  9930.     {
  9931.         *aBuf++ = aVarName[4] ? '0': '1';
  9932.         *aBuf = '\0';
  9933.     }
  9934.     return 1; // The length of the value.
  9935. }
  9936.  
  9937. VarSizeType BIV_MMM_DDD(char *aBuf, char *aVarName)
  9938. {
  9939.     char *format_str;
  9940.     switch(toupper(aVarName[2]))
  9941.     {
  9942.     // Use the case-sensitive formats required by GetDateFormat():
  9943.     case 'M': format_str = (aVarName[5] ? "MMMM" : "MMM"); break;
  9944.     case 'D': format_str = (aVarName[5] ? "dddd" : "ddd"); break;
  9945.     }
  9946.     // Confirmed: The below will automatically use the local time (not UTC) when 3rd param is NULL.
  9947.     return (VarSizeType)(GetDateFormat(LOCALE_USER_DEFAULT, 0, NULL, format_str, aBuf, aBuf ? 999 : 0) - 1);
  9948. }
  9949.  
  9950. VarSizeType BIV_DateTime(char *aBuf, char *aVarName)
  9951. {
  9952.     if (!aBuf)
  9953.         return 6; // Since only an estimate is needed in this mode, return the maximum length of any item.
  9954.  
  9955.     aVarName += 2; // Skip past the "A_".
  9956.  
  9957.     // The current time is refreshed only if it's been a certain number of milliseconds since
  9958.     // the last fetch of one of these built-in time variables.  This keeps the variables in
  9959.     // sync with one another when they are used consecutively such as this example:
  9960.     // Var = %A_Hour%:%A_Min%:%A_Sec%
  9961.     // Using GetTickCount() because it's very low overhead compared to the other time functions:
  9962.     static DWORD sLastUpdate = 0; // Static should be thread + recursion safe in this case.
  9963.     static SYSTEMTIME sST = {0}; // Init to detect when it's empty.
  9964.     BOOL is_msec = !stricmp(aVarName, "MSec"); // Always refresh if it's milliseconds, for better accuracy.
  9965.     DWORD now_tick = GetTickCount();
  9966.     if (is_msec || now_tick - sLastUpdate > 50 || !sST.wYear) // See comments above.
  9967.     {
  9968.         GetLocalTime(&sST);
  9969.         sLastUpdate = now_tick;
  9970.     }
  9971.  
  9972.     if (is_msec)
  9973.         return sprintf(aBuf, "%03d", sST.wMilliseconds);
  9974.  
  9975.     char second_letter = toupper(aVarName[1]);
  9976.     switch(toupper(aVarName[0]))
  9977.     {
  9978.     case 'Y':
  9979.         switch(second_letter)
  9980.         {
  9981.         case 'D': // A_YDay
  9982.             return sprintf(aBuf, "%d", GetYDay(sST.wMonth, sST.wDay, IS_LEAP_YEAR(sST.wYear)));
  9983.         case 'W': // A_YWeek
  9984.             return GetISOWeekNumber(aBuf, sST.wYear
  9985.                 , GetYDay(sST.wMonth, sST.wDay, IS_LEAP_YEAR(sST.wYear))
  9986.                 , sST.wDayOfWeek);
  9987.         default:  // A_Year/A_YYYY
  9988.             return sprintf(aBuf, "%d", sST.wYear);
  9989.         }
  9990.         // No break because all cases above return:
  9991.         //break;
  9992.     case 'M':
  9993.         switch(second_letter)
  9994.         {
  9995.         case 'D': // A_MDay (synonymous with A_DD)
  9996.             return sprintf(aBuf, "%02d", sST.wDay);
  9997.         case 'I': // A_Min
  9998.             return sprintf(aBuf, "%02d", sST.wMinute);
  9999.         default: // A_MM and A_Mon (A_MSec was already completely handled higher above).
  10000.             return sprintf(aBuf, "%02d", sST.wMonth);
  10001.         }
  10002.         // No break because all cases above return:
  10003.         //break;
  10004.     case 'D': // A_DD (synonymous with A_MDay)
  10005.         return sprintf(aBuf, "%02d", sST.wDay);
  10006.     case 'W': // A_WDay
  10007.         return sprintf(aBuf, "%d", sST.wDayOfWeek + 1);
  10008.     case 'H': // A_Hour
  10009.         return sprintf(aBuf, "%02d", sST.wHour);
  10010.     case 'S': // A_Sec (A_MSec was already completely handled higher above).
  10011.         return sprintf(aBuf, "%02d", sST.wSecond);
  10012.     }
  10013.     return 0; // Never reached, but avoids compiler warning.
  10014. }
  10015.  
  10016. VarSizeType BIV_BatchLines(char *aBuf, char *aVarName)
  10017. {
  10018.     // The BatchLine value can be either a numerical string or a string that ends in "ms".
  10019.     char buf[256];
  10020.     char *target_buf = aBuf ? aBuf : buf;
  10021.     if (g.IntervalBeforeRest > -1) // Have this new method take precedence, if it's in use by the script.
  10022.         return sprintf(target_buf, "%dms", g.IntervalBeforeRest); // Not snprintf().
  10023.     // Otherwise:
  10024.     ITOA64(g.LinesPerCycle, target_buf);
  10025.     return (VarSizeType)strlen(target_buf);
  10026. }
  10027.  
  10028. VarSizeType BIV_TitleMatchMode(char *aBuf, char *aVarName)
  10029. {
  10030.     if (g.TitleMatchMode == FIND_REGEX) // v1.0.45.
  10031.     {
  10032.         if (aBuf)  // For backward compatibility (due to StringCaseSense), never change the case used here:
  10033.             strcpy(aBuf, "RegEx");
  10034.         return 5; // The length.
  10035.     }
  10036.     // Otherwise, it's a numerical mode:
  10037.     // It's done this way in case it's ever allowed to go beyond a single-digit number.
  10038.     char buf[MAX_NUMBER_SIZE];
  10039.     char *target_buf = aBuf ? aBuf : buf;
  10040.     _itoa(g.TitleMatchMode, target_buf, 10);  // Always output as decimal vs. hex in this case (so that scripts can use "If var in list" with confidence).
  10041.     return (VarSizeType)strlen(target_buf);
  10042. }
  10043.  
  10044. VarSizeType BIV_TitleMatchModeSpeed(char *aBuf, char *aVarName)
  10045. {
  10046.     if (aBuf)  // For backward compatibility (due to StringCaseSense), never change the case used here:
  10047.         strcpy(aBuf, g.TitleFindFast ? "Fast" : "Slow");
  10048.     return 4;  // Always length 4
  10049. }
  10050.  
  10051. VarSizeType BIV_DetectHiddenWindows(char *aBuf, char *aVarName)
  10052. {
  10053.     return aBuf
  10054.         ? (VarSizeType)strlen(strcpy(aBuf, g.DetectHiddenWindows ? "On" : "Off")) // For backward compatibility (due to StringCaseSense), never change the case used here.  Fixed in v1.0.42.01 to return exact length (required).
  10055.         : 3; // Room for either On or Off (in the estimation phase).
  10056. }
  10057.  
  10058. VarSizeType BIV_DetectHiddenText(char *aBuf, char *aVarName)
  10059. {
  10060.     return aBuf
  10061.         ? (VarSizeType)strlen(strcpy(aBuf, g.DetectHiddenText ? "On" : "Off")) // For backward compatibility (due to StringCaseSense), never change the case used here. Fixed in v1.0.42.01 to return exact length (required).
  10062.         : 3; // Room for either On or Off (in the estimation phase).
  10063. }
  10064.  
  10065. VarSizeType BIV_AutoTrim(char *aBuf, char *aVarName)
  10066. {
  10067.     return aBuf
  10068.         ? (VarSizeType)strlen(strcpy(aBuf, g.AutoTrim ? "On" : "Off")) // For backward compatibility (due to StringCaseSense), never change the case used here. Fixed in v1.0.42.01 to return exact length (required).
  10069.         : 3; // Room for either On or Off (in the estimation phase).
  10070. }
  10071.  
  10072. VarSizeType BIV_StringCaseSense(char *aBuf, char *aVarName)
  10073. {
  10074.     return aBuf
  10075.         ? (VarSizeType)strlen(strcpy(aBuf, g.StringCaseSense == SCS_INSENSITIVE ? "Off" // For backward compatibility (due to StringCaseSense), never change the case used here.  Fixed in v1.0.42.01 to return exact length (required).
  10076.             : (g.StringCaseSense == SCS_SENSITIVE ? "On" : "Locale")))
  10077.         : 6; // Room for On, Off, or Locale (in the estimation phase).
  10078. }
  10079.  
  10080. VarSizeType BIV_FormatInteger(char *aBuf, char *aVarName)
  10081. {
  10082.     if (aBuf)
  10083.     {
  10084.         // For backward compatibility (due to StringCaseSense), never change the case used here:
  10085.         *aBuf++ = g.FormatIntAsHex ? 'H' : 'D';
  10086.         *aBuf = '\0';
  10087.     }
  10088.     return 1;
  10089. }
  10090.  
  10091. VarSizeType BIV_FormatFloat(char *aBuf, char *aVarName)
  10092. {
  10093.     if (!aBuf)
  10094.         return (VarSizeType)strlen(g.FormatFloat);  // Include the extra chars since this is just an estimate.
  10095.     char *str_with_leading_percent_omitted = g.FormatFloat + 1;
  10096.     size_t length = strlen(str_with_leading_percent_omitted);
  10097.     strlcpy(aBuf, str_with_leading_percent_omitted
  10098.         , length + !(length && str_with_leading_percent_omitted[length-1] == 'f')); // Omit the trailing character only if it's an 'f', not any other letter such as the 'e' in "%0.6e" (for backward compatibility).
  10099.     return (VarSizeType)strlen(aBuf); // Must return exact length when aBuf isn't NULL.
  10100. }
  10101.  
  10102. VarSizeType BIV_KeyDelay(char *aBuf, char *aVarName)
  10103. {
  10104.     char buf[MAX_NUMBER_SIZE];
  10105.     char *target_buf = aBuf ? aBuf : buf;
  10106.     _itoa(g.KeyDelay, target_buf, 10);  // Always output as decimal vs. hex in this case (so that scripts can use "If var in list" with confidence).
  10107.     return (VarSizeType)strlen(target_buf);
  10108. }
  10109.  
  10110. VarSizeType BIV_WinDelay(char *aBuf, char *aVarName)
  10111. {
  10112.     char buf[MAX_NUMBER_SIZE];
  10113.     char *target_buf = aBuf ? aBuf : buf;
  10114.     _itoa(g.WinDelay, target_buf, 10);  // Always output as decimal vs. hex in this case (so that scripts can use "If var in list" with confidence).
  10115.     return (VarSizeType)strlen(target_buf);
  10116. }
  10117.  
  10118. VarSizeType BIV_ControlDelay(char *aBuf, char *aVarName)
  10119. {
  10120.     char buf[MAX_NUMBER_SIZE];
  10121.     char *target_buf = aBuf ? aBuf : buf;
  10122.     _itoa(g.ControlDelay, target_buf, 10);  // Always output as decimal vs. hex in this case (so that scripts can use "If var in list" with confidence).
  10123.     return (VarSizeType)strlen(target_buf);
  10124. }
  10125.  
  10126. VarSizeType BIV_MouseDelay(char *aBuf, char *aVarName)
  10127. {
  10128.     char buf[MAX_NUMBER_SIZE];
  10129.     char *target_buf = aBuf ? aBuf : buf;
  10130.     _itoa(g.MouseDelay, target_buf, 10);  // Always output as decimal vs. hex in this case (so that scripts can use "If var in list" with confidence).
  10131.     return (VarSizeType)strlen(target_buf);
  10132. }
  10133.  
  10134. VarSizeType BIV_DefaultMouseSpeed(char *aBuf, char *aVarName)
  10135. {
  10136.     char buf[MAX_NUMBER_SIZE];
  10137.     char *target_buf = aBuf ? aBuf : buf;
  10138.     _itoa(g.DefaultMouseSpeed, target_buf, 10);  // Always output as decimal vs. hex in this case (so that scripts can use "If var in list" with confidence).
  10139.     return (VarSizeType)strlen(target_buf);
  10140. }
  10141.  
  10142. VarSizeType BIV_IsSuspended(char *aBuf, char *aVarName)
  10143. {
  10144.     if (aBuf)
  10145.     {
  10146.         *aBuf++ = g_IsSuspended ? '1' : '0';
  10147.         *aBuf = '\0';
  10148.     }
  10149.     return 1;
  10150. }
  10151.  
  10152. #ifdef AUTOHOTKEYSC  // A_IsCompiled is left blank/undefined in uncompiled scripts.
  10153. VarSizeType BIV_IsCompiled(char *aBuf, char *aVarName)
  10154. {
  10155.     if (aBuf)
  10156.     {
  10157.         *aBuf++ = '1';
  10158.         *aBuf = '\0';
  10159.     }
  10160.     return 1;
  10161. }
  10162. #endif
  10163.  
  10164. VarSizeType BIV_LastError(char *aBuf, char *aVarName)
  10165. {
  10166.     char buf[MAX_NUMBER_SIZE];
  10167.     char *target_buf = aBuf ? aBuf : buf;
  10168.     _itoa(g.LastError, target_buf, 10);  // Always output as decimal vs. hex in this case (so that scripts can use "If var in list" with confidence).
  10169.     return (VarSizeType)strlen(target_buf);
  10170. }
  10171.  
  10172.  
  10173.  
  10174. VarSizeType BIV_IconHidden(char *aBuf, char *aVarName)
  10175. {
  10176.     if (aBuf)
  10177.     {
  10178.         *aBuf++ = g_NoTrayIcon ? '1' : '0';
  10179.         *aBuf = '\0';
  10180.     }
  10181.     return 1;  // Length is always 1.
  10182. }
  10183.  
  10184. VarSizeType BIV_IconTip(char *aBuf, char *aVarName)
  10185. {
  10186.     if (!aBuf)
  10187.         return g_script.mTrayIconTip ? (VarSizeType)strlen(g_script.mTrayIconTip) : 0;
  10188.     if (g_script.mTrayIconTip)
  10189.         return (VarSizeType)strlen(strcpy(aBuf, g_script.mTrayIconTip));
  10190.     else
  10191.     {
  10192.         *aBuf = '\0';
  10193.         return 0;
  10194.     }
  10195. }
  10196.  
  10197. VarSizeType BIV_IconFile(char *aBuf, char *aVarName)
  10198. {
  10199.     if (!aBuf)
  10200.         return g_script.mCustomIconFile ? (VarSizeType)strlen(g_script.mCustomIconFile) : 0;
  10201.     if (g_script.mCustomIconFile)
  10202.         return (VarSizeType)strlen(strcpy(aBuf, g_script.mCustomIconFile));
  10203.     else
  10204.     {
  10205.         *aBuf = '\0';
  10206.         return 0;
  10207.     }
  10208. }
  10209.  
  10210. VarSizeType BIV_IconNumber(char *aBuf, char *aVarName)
  10211. {
  10212.     char buf[MAX_NUMBER_SIZE];
  10213.     char *target_buf = aBuf ? aBuf : buf;
  10214.     if (!g_script.mCustomIconNumber) // Yield an empty string rather than the digit "0".
  10215.     {
  10216.         *target_buf = '\0';
  10217.         return 0;
  10218.     }
  10219.     return (VarSizeType)strlen(UTOA(g_script.mCustomIconNumber, target_buf));
  10220. }
  10221.  
  10222.  
  10223.  
  10224. VarSizeType BIV_ExitReason(char *aBuf, char *aVarName)
  10225. {
  10226.     char *str;
  10227.     switch(g_script.mExitReason)
  10228.     {
  10229.     case EXIT_LOGOFF: str = "Logoff"; break;
  10230.     case EXIT_SHUTDOWN: str = "Shutdown"; break;
  10231.     // Since the below are all relatively rare, except WM_CLOSE perhaps, they are all included
  10232.     // as one word to cut down on the number of possible words (it's easier to write OnExit
  10233.     // routines to cover all possibilities if there are fewer of them).
  10234.     case EXIT_WM_QUIT:
  10235.     case EXIT_CRITICAL:
  10236.     case EXIT_DESTROY:
  10237.     case EXIT_WM_CLOSE: str = "Close"; break;
  10238.     case EXIT_ERROR: str = "Error"; break;
  10239.     case EXIT_MENU: str = "Menu"; break;  // Standard menu, not a user-defined menu.
  10240.     case EXIT_EXIT: str = "Exit"; break;  // ExitApp or Exit command.
  10241.     case EXIT_RELOAD: str = "Reload"; break;
  10242.     case EXIT_SINGLEINSTANCE: str = "Single"; break;
  10243.     default:  // EXIT_NONE or unknown value (unknown would be considered a bug if it ever happened).
  10244.         str = "";
  10245.     }
  10246.     if (aBuf)
  10247.         strcpy(aBuf, str);
  10248.     return (VarSizeType)strlen(str);
  10249. }
  10250.  
  10251.  
  10252.  
  10253. VarSizeType BIV_Space_Tab(char *aBuf, char *aVarName)
  10254. {
  10255.     // Really old comment:
  10256.     // A_Space is a built-in variable rather than using an escape sequence such as `s, because the escape
  10257.     // sequence method doesn't work (probably because `s resolves to a space and is that trimmed at
  10258.     // some point in process prior to when it can be used):
  10259.     if (aBuf)
  10260.     {
  10261.         *aBuf++ = aVarName[5] ? ' ' : '\t'; // A_Tab[]
  10262.         *aBuf = '\0';
  10263.     }
  10264.     return 1;
  10265. }
  10266.  
  10267. VarSizeType BIV_AhkVersion(char *aBuf, char *aVarName)
  10268. {
  10269.     if (aBuf)
  10270.         strcpy(aBuf, NAME_VERSION);
  10271.     return (VarSizeType)strlen(NAME_VERSION);
  10272. }
  10273.  
  10274. VarSizeType BIV_AhkPath(char *aBuf, char *aVarName) // v1.0.41.
  10275. {
  10276. #ifdef AUTOHOTKEYSC
  10277.     if (aBuf)
  10278.     {
  10279.         size_t length;
  10280.         if (length = GetAHKInstallDir(aBuf))
  10281.             // Name "AutoHotkey.exe" is assumed for code size reduction and because it's not stored in the registry:
  10282.             strlcpy(aBuf + length, "\\AutoHotkey.exe", MAX_PATH - length); // strlcpy() in case registry has a path that is too close to MAX_PATH to fit AutoHotkey.exe
  10283.         //else leave it blank as documented.
  10284.         return (VarSizeType)strlen(aBuf);
  10285.     }
  10286.     // Otherwise: Always return an estimate of MAX_PATH in case the registry entry changes between the
  10287.     // first call and the second.  This is also relied upon by strlcpy() above, which zero-fills the tail
  10288.     // of the destination up through the limit of its capacity (due to calling strncpy, which does this).
  10289.     return MAX_PATH;
  10290. #else
  10291.     char buf[MAX_PATH];
  10292.     VarSizeType length = (VarSizeType)GetModuleFileName(NULL, buf, MAX_PATH);
  10293.     if (aBuf)
  10294.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string). This is true for ReadRegString()'s API call and may be true for other API calls like this one.
  10295.     return length;
  10296. #endif
  10297. }
  10298.  
  10299.  
  10300.  
  10301. VarSizeType BIV_TickCount(char *aBuf, char *aVarName)
  10302. {
  10303.     // UPDATE: The below comments are now obsolete in light of having switched over to
  10304.     // using 64-bit integers (which aren't that much slower than 32-bit on 32-bit hardware):
  10305.     // Known limitation:
  10306.     // Although TickCount is an unsigned value, I'm not sure that our EnvSub command
  10307.     // will properly be able to compare two tick-counts if either value is larger than
  10308.     // INT_MAX.  So if the system has been up for more than about 25 days, there might be
  10309.     // problems if the user tries compare two tick-counts in the script using EnvSub.
  10310.     // UPDATE: It seems better to store all unsigned values as signed within script
  10311.     // variables.  Otherwise, when the var's value is next accessed and converted using
  10312.     // ATOI(), the outcome won't be as useful.  In other words, since the negative value
  10313.     // will be properly converted by ATOI(), comparing two negative tickcounts works
  10314.     // correctly (confirmed).  Even if one of them is negative and the other positive,
  10315.     // it will probably work correctly due to the nature of implicit unsigned math.
  10316.     // Thus, we use %d vs. %u in the snprintf() call below.
  10317.     return aBuf
  10318.         ? (VarSizeType)strlen(ITOA64(GetTickCount(), aBuf))
  10319.         : MAX_NUMBER_LENGTH; // IMPORTANT: Conservative estimate because tick might change between 1st & 2nd calls.
  10320. }
  10321.  
  10322.  
  10323.  
  10324. VarSizeType BIV_Now(char *aBuf, char *aVarName)
  10325. {
  10326.     if (!aBuf)
  10327.         return DATE_FORMAT_LENGTH;
  10328.     SYSTEMTIME st;
  10329.     if (aVarName[5]) // A_Now[U]TC
  10330.         GetSystemTime(&st);
  10331.     else
  10332.         GetLocalTime(&st);
  10333.     SystemTimeToYYYYMMDD(aBuf, st);
  10334.     return (VarSizeType)strlen(aBuf);
  10335. }
  10336.  
  10337. VarSizeType BIV_OSType(char *aBuf, char *aVarName)
  10338. {
  10339.     char *type = g_os.IsWinNT() ? "WIN32_NT" : "WIN32_WINDOWS";
  10340.     if (aBuf)
  10341.         strcpy(aBuf, type);
  10342.     return (VarSizeType)strlen(type); // Return length of type, not aBuf.
  10343. }
  10344.  
  10345. VarSizeType BIV_OSVersion(char *aBuf, char *aVarName)
  10346. {
  10347.     char *version = "";  // Init in case OS is something later than Win2003.
  10348.     if (g_os.IsWinNT()) // "NT" includes all NT-kernal OSes: NT4/2000/XP/2003/Vista.
  10349.     {
  10350.         if (g_os.IsWinXP())
  10351.             version = "WIN_XP";
  10352.         else if (g_os.IsWinVista())
  10353.             version = "WIN_VISTA";
  10354.         else if (g_os.IsWin2003())
  10355.             version = "WIN_2003";
  10356.         else
  10357.         {
  10358.             if (g_os.IsWin2000())
  10359.                 version = "WIN_2000";
  10360.             else
  10361.                 version = "WIN_NT4";
  10362.         }
  10363.     }
  10364.     else
  10365.     {
  10366.         if (g_os.IsWin95())
  10367.             version = "WIN_95";
  10368.         else
  10369.         {
  10370.             if (g_os.IsWin98())
  10371.                 version = "WIN_98";
  10372.             else
  10373.                 version = "WIN_ME";
  10374.         }
  10375.     }
  10376.     if (aBuf)
  10377.         strcpy(aBuf, version);
  10378.     return (VarSizeType)strlen(version); // Always return the length of version, not aBuf.
  10379. }
  10380.  
  10381. VarSizeType BIV_Language(char *aBuf, char *aVarName)
  10382. // Registry locations from J-Paul Mesnage.
  10383. {
  10384.     char buf[MAX_PATH];
  10385.     VarSizeType length;
  10386.     if (g_os.IsWinNT())  // NT/2k/XP+
  10387.         length = g_os.IsWin2000orLater()
  10388.             ? ReadRegString(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Nls\\Language", "InstallLanguage", buf, MAX_PATH)
  10389.             : ReadRegString(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Nls\\Language", "Default", buf, MAX_PATH); // NT4
  10390.     else // Win9x
  10391.     {
  10392.         length = ReadRegString(HKEY_USERS, ".DEFAULT\\Control Panel\\Desktop\\ResourceLocale", "", buf, MAX_PATH);
  10393.         if (length > 3)
  10394.         {
  10395.             length -= 4;
  10396.             memmove(buf, buf + 4, length + 1); // +1 to include the zero terminator.
  10397.         }
  10398.     }
  10399.     if (aBuf)
  10400.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string).
  10401.     return length;
  10402. }
  10403.  
  10404. VarSizeType BIV_UserName_ComputerName(char *aBuf, char *aVarName)
  10405. {
  10406.     char buf[MAX_PATH];  // Doesn't use MAX_COMPUTERNAME_LENGTH + 1 in case longer names are allowed in the future.
  10407.     DWORD buf_size = MAX_PATH; // Below: A_Computer[N]ame (N is the 11th char, index 10, which if present at all distinguishes between the two).
  10408.     if (   !(aVarName[10] ? GetComputerName(buf, &buf_size) : GetUserName(buf, &buf_size))   )
  10409.         *buf = '\0';
  10410.     if (aBuf)
  10411.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string). This is true for ReadRegString()'s API call and may be true for other API calls like the ones here.
  10412.     return (VarSizeType)strlen(buf); // I seem to remember that the lengths returned from the above API calls aren't consistent in these cases.
  10413. }
  10414.  
  10415. VarSizeType BIV_WorkingDir(char *aBuf, char *aVarName)
  10416. {
  10417.     // Use GetCurrentDirectory() vs. g_WorkingDir because any in-progress FileSelectFile()
  10418.     // dialog is able to keep functioning even when it's quasi-thread is suspended.  The
  10419.     // dialog can thus change the current directory as seen by the active quasi-thread even
  10420.     // though g_WorkingDir hasn't been updated.  It might also be possible for the working
  10421.     // directory to change in unusual circumstances such as a network drive being lost).
  10422.     //
  10423.     // Fix for v1.0.43.11: Changed size below from 9999 to MAX_PATH, otherwise it fails sometimes on Win9x.
  10424.     // Testing shows that the failure is not caused by GetCurrentDirectory() writing to the unused part of the
  10425.     // buffer, such as zeroing it (which is good because that would require this part to be redesigned to pass
  10426.     // the actual buffer size or use a temp buffer).  So there's something else going on to explain why the
  10427.     // problem only occurs in longer scripts on Win98se, not in trivial ones such as Var=%A_WorkingDir%.
  10428.     // Nor did the problem affect expression assignments such as Var:=A_WorkingDir.
  10429.     char buf[MAX_PATH];
  10430.     VarSizeType length = GetCurrentDirectory(MAX_PATH, buf);
  10431.     if (aBuf)
  10432.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string). This is true for ReadRegString()'s API call and may be true for other API calls like the one here.
  10433.     return length;
  10434.     // Formerly the following, but I don't think it's as reliable/future-proof given the 1.0.47 comment above:
  10435.     //return aBuf
  10436.     //    ? GetCurrentDirectory(MAX_PATH, aBuf)
  10437.     //    : GetCurrentDirectory(0, NULL); // MSDN says that this is a valid way to call it on all OSes, and testing shows that it works on WinXP and 98se.
  10438.         // Above avoids subtracting 1 to be conservative and to reduce code size (due to the need to otherwise check for zero and avoid subtracting 1 in that case).
  10439. }
  10440.  
  10441. VarSizeType BIV_WinDir(char *aBuf, char *aVarName)
  10442. {
  10443.     char buf[MAX_PATH];
  10444.     VarSizeType length = GetWindowsDirectory(buf, MAX_PATH);
  10445.     if (aBuf)
  10446.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string). This is true for ReadRegString()'s API call and may be true for other API calls like the one here.
  10447.     return length;
  10448.     // Formerly the following, but I don't think it's as reliable/future-proof given the 1.0.47 comment above:
  10449.     //char buf_temp[1]; // Just a fake buffer to pass to some API functions in lieu of a NULL, to avoid any chance of misbehavior. Keep the size at 1 so that API functions will always fail to copy to buf.
  10450.     //// Sizes/lengths/-1/return-values/etc. have been verified correct.
  10451.     //return aBuf
  10452.     //    ? GetWindowsDirectory(aBuf, MAX_PATH) // MAX_PATH is kept in case it's needed on Win9x for reasons similar to those in GetEnvironmentVarWin9x().
  10453.     //    : GetWindowsDirectory(buf_temp, 0);
  10454.         // Above avoids subtracting 1 to be conservative and to reduce code size (due to the need to otherwise check for zero and avoid subtracting 1 in that case).
  10455. }
  10456.  
  10457. VarSizeType BIV_Temp(char *aBuf, char *aVarName)
  10458. {
  10459.     char buf[MAX_PATH];
  10460.     VarSizeType length = GetTempPath(MAX_PATH, buf);
  10461.     if (aBuf)
  10462.     {
  10463.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string). This is true for ReadRegString()'s API call and may be true for other API calls like the one here.
  10464.         if (length)
  10465.         {
  10466.             aBuf += length - 1;
  10467.             if (*aBuf == '\\') // For some reason, it typically yields a trailing backslash, so omit it to improve friendliness/consistency.
  10468.             {
  10469.                 *aBuf = '\0';
  10470.                 --length;
  10471.             }
  10472.         }
  10473.     }
  10474.     return length;
  10475. }
  10476.  
  10477. VarSizeType BIV_ComSpec(char *aBuf, char *aVarName)
  10478. {
  10479.     char buf_temp[1]; // Just a fake buffer to pass to some API functions in lieu of a NULL, to avoid any chance of misbehavior. Keep the size at 1 so that API functions will always fail to copy to buf.
  10480.     // Sizes/lengths/-1/return-values/etc. have been verified correct.
  10481.     return aBuf ? GetEnvVarReliable("comspec", aBuf) // v1.0.46.08: GetEnvVarReliable() fixes %Comspec% on Windows 9x.
  10482.         : GetEnvironmentVariable("comspec", buf_temp, 0); // Avoids subtracting 1 to be conservative and to reduce code size (due to the need to otherwise check for zero and avoid subtracting 1 in that case).
  10483. }
  10484.  
  10485. VarSizeType BIV_ProgramFiles(char *aBuf, char *aVarName)
  10486. {
  10487.     char buf[MAX_PATH];
  10488.     VarSizeType length = ReadRegString(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion", "ProgramFilesDir", buf, MAX_PATH);
  10489.     if (aBuf)
  10490.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string).
  10491.     return length;
  10492. }
  10493.  
  10494. VarSizeType BIV_AppData(char *aBuf, char *aVarName) // Called by multiple callers.
  10495. {
  10496.     char buf[MAX_PATH]; // One caller relies on this being explicitly limited to MAX_PATH.
  10497.     VarSizeType length = aVarName[9] // A_AppData[C]ommon
  10498.         ? ReadRegString(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"
  10499.             , "Common AppData", buf, MAX_PATH)
  10500.         : 0;
  10501.     if (!length) // Either the above failed or we were told to get the user/private dir instead.
  10502.         length = ReadRegString(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"
  10503.             , "AppData", buf, MAX_PATH);
  10504.     if (aBuf)
  10505.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string).
  10506.     return length;
  10507. }
  10508.  
  10509. VarSizeType BIV_Desktop(char *aBuf, char *aVarName)
  10510. {
  10511.     char buf[MAX_PATH];
  10512.     VarSizeType length = aVarName[9] // A_Desktop[C]ommon
  10513.         ? ReadRegString(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", "Common Desktop", buf, MAX_PATH)
  10514.         : 0;
  10515.     if (!length) // Either the above failed or we were told to get the user/private dir instead.
  10516.         length = ReadRegString(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", "Desktop", buf, MAX_PATH);
  10517.     if (aBuf)
  10518.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string).
  10519.     return length;
  10520. }
  10521.  
  10522. VarSizeType BIV_StartMenu(char *aBuf, char *aVarName)
  10523. {
  10524.     char buf[MAX_PATH];
  10525.     VarSizeType length = aVarName[11] // A_StartMenu[C]ommon
  10526.         ? ReadRegString(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", "Common Start Menu", buf, MAX_PATH)
  10527.         : 0;
  10528.     if (!length) // Either the above failed or we were told to get the user/private dir instead.
  10529.         length = ReadRegString(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", "Start Menu", buf, MAX_PATH);
  10530.     if (aBuf)
  10531.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string).
  10532.     return length;
  10533. }
  10534.  
  10535. VarSizeType BIV_Programs(char *aBuf, char *aVarName)
  10536. {
  10537.     char buf[MAX_PATH];
  10538.     VarSizeType length = aVarName[10] // A_Programs[C]ommon
  10539.         ? ReadRegString(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", "Common Programs", buf, MAX_PATH)
  10540.         : 0;
  10541.     if (!length) // Either the above failed or we were told to get the user/private dir instead.
  10542.         length = ReadRegString(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", "Programs", buf, MAX_PATH);
  10543.     if (aBuf)
  10544.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string).
  10545.     return length;
  10546. }
  10547.  
  10548. VarSizeType BIV_Startup(char *aBuf, char *aVarName)
  10549. {
  10550.     char buf[MAX_PATH];
  10551.     VarSizeType length = aVarName[9] // A_Startup[C]ommon
  10552.         ? ReadRegString(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", "Common Startup", buf, MAX_PATH)
  10553.         : 0;
  10554.     if (!length) // Either the above failed or we were told to get the user/private dir instead.
  10555.         length = ReadRegString(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", "Startup", buf, MAX_PATH);
  10556.     if (aBuf)
  10557.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string).
  10558.     return length;
  10559. }
  10560.  
  10561. VarSizeType BIV_MyDocuments(char *aBuf, char *aVarName) // Called by multiple callers.
  10562. {
  10563.     char buf[MAX_PATH];
  10564.     ReadRegString(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"
  10565.         , "Personal", buf, MAX_PATH); // Some callers might rely on MAX_PATH being the limit, to avoid overflow.
  10566.     // Since it is common (such as in networked environments) to have My Documents on the root of a drive
  10567.     // (such as a mapped drive letter), remove the backslash from something like M:\ because M: is more
  10568.     // appropriate for most uses:
  10569.     VarSizeType length = (VarSizeType)strip_trailing_backslash(buf);
  10570.     if (aBuf)
  10571.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string).
  10572.     return length;
  10573. }
  10574.  
  10575.  
  10576.  
  10577. VarSizeType BIV_Caret(char *aBuf, char *aVarName)
  10578. {
  10579.     if (!aBuf)
  10580.         return MAX_NUMBER_LENGTH; // Conservative, both for performance and in case the value changes between first and second call.
  10581.  
  10582.     // These static variables are used to keep the X and Y coordinates in sync with each other, as a snapshot
  10583.     // of where the caret was at one precise instant in time.  This is because the X and Y vars are resolved
  10584.     // separately by the script, and due to split second timing, they might otherwise not be accurate with
  10585.     // respect to each other.  This method also helps performance since it avoids unnecessary calls to
  10586.     // ATTACH_THREAD_INPUT.
  10587.     static HWND sForeWinPrev = NULL;
  10588.     static DWORD sTimestamp = GetTickCount();
  10589.     static POINT sPoint;
  10590.     static BOOL sResult;
  10591.  
  10592.     // I believe only the foreground window can have a caret position due to relationship with focused control.
  10593.     HWND target_window = GetForegroundWindow(); // Variable must be named target_window for ATTACH_THREAD_INPUT.
  10594.     if (!target_window) // No window is in the foreground, report blank coordinate.
  10595.     {
  10596.         *aBuf = '\0';
  10597.         return 0;
  10598.     }
  10599.  
  10600.     DWORD now_tick = GetTickCount();
  10601.  
  10602.     if (target_window != sForeWinPrev || now_tick - sTimestamp > 5) // Different window or too much time has passed.
  10603.     {
  10604.         // Otherwise:
  10605.         ATTACH_THREAD_INPUT
  10606.         sResult = GetCaretPos(&sPoint);
  10607.         HWND focused_control = GetFocus();  // Also relies on threads being attached.
  10608.         DETACH_THREAD_INPUT
  10609.         if (!sResult)
  10610.         {
  10611.             *aBuf = '\0';
  10612.             return 0;
  10613.         }
  10614.         ClientToScreen(focused_control ? focused_control : target_window, &sPoint);
  10615.         if (!(g.CoordMode & COORD_MODE_CARET))  // Using the default, which is coordinates relative to window.
  10616.             ScreenToWindow(sPoint, target_window);
  10617.         // Now that all failure conditions have been checked, update static variables for the next caller:
  10618.         sForeWinPrev = target_window;
  10619.         sTimestamp = now_tick;
  10620.     }
  10621.     else // Same window and recent enough, but did prior call fail?  If so, provide a blank result like the prior.
  10622.     {
  10623.         if (!sResult)
  10624.         {
  10625.             *aBuf = '\0';
  10626.             return 0;
  10627.         }
  10628.     }
  10629.     // Now the above has ensured that sPoint contains valid coordinates that are up-to-date enough to be used.
  10630.     _itoa(toupper(aVarName[7]) == 'X' ? sPoint.x : sPoint.y, aBuf, 10);  // Always output as decimal vs. hex in this case (so that scripts can use "If var in list" with confidence).
  10631.     return (VarSizeType)strlen(aBuf);
  10632. }
  10633.  
  10634.  
  10635.  
  10636. VarSizeType BIV_Cursor(char *aBuf, char *aVarName)
  10637. {
  10638.     if (!aBuf)
  10639.         return SMALL_STRING_LENGTH;  // We're returning the length of the var's contents, not the size.
  10640.  
  10641.     // Must fetch it at runtime, otherwise the program can't even be launched on Windows 95:
  10642.     typedef BOOL (WINAPI *MyGetCursorInfoType)(PCURSORINFO);
  10643.     static MyGetCursorInfoType MyGetCursorInfo = (MyGetCursorInfoType)GetProcAddress(GetModuleHandle("user32"), "GetCursorInfo");
  10644.  
  10645.     HCURSOR current_cursor;
  10646.     if (MyGetCursorInfo) // v1.0.42.02: This method is used to avoid ATTACH_THREAD_INPUT, which interferes with double-clicking if called repeatedly at a high frequency.
  10647.     {
  10648.         CURSORINFO ci;
  10649.         ci.cbSize = sizeof(CURSORINFO);
  10650.         current_cursor = MyGetCursorInfo(&ci) ? ci.hCursor : NULL;
  10651.     }
  10652.     else // Windows 95 and old-service-pack versions of NT4 require the old method.
  10653.     {
  10654.         POINT point;
  10655.         GetCursorPos(&point);
  10656.         HWND target_window = WindowFromPoint(point);
  10657.  
  10658.         // MSDN docs imply that threads must be attached for GetCursor() to work.
  10659.         // A side-effect of attaching threads or of GetCursor() itself is that mouse double-clicks
  10660.         // are interfered with, at least if this function is called repeatedly at a high frequency.
  10661.         ATTACH_THREAD_INPUT
  10662.         current_cursor = GetCursor();
  10663.         DETACH_THREAD_INPUT
  10664.     }
  10665.  
  10666.     if (!current_cursor)
  10667.     {
  10668.         #define CURSOR_UNKNOWN "Unknown"
  10669.         strlcpy(aBuf, CURSOR_UNKNOWN, SMALL_STRING_LENGTH + 1);
  10670.         return (VarSizeType)strlen(aBuf);
  10671.     }
  10672.  
  10673.     // Static so that it's initialized on first use (should help performance after the first time):
  10674.     static HCURSOR sCursor[] = {LoadCursor(NULL, IDC_APPSTARTING), LoadCursor(NULL, IDC_ARROW)
  10675.         , LoadCursor(NULL, IDC_CROSS), LoadCursor(NULL, IDC_HELP), LoadCursor(NULL, IDC_IBEAM)
  10676.         , LoadCursor(NULL, IDC_ICON), LoadCursor(NULL, IDC_NO), LoadCursor(NULL, IDC_SIZE)
  10677.         , LoadCursor(NULL, IDC_SIZEALL), LoadCursor(NULL, IDC_SIZENESW), LoadCursor(NULL, IDC_SIZENS)
  10678.         , LoadCursor(NULL, IDC_SIZENWSE), LoadCursor(NULL, IDC_SIZEWE), LoadCursor(NULL, IDC_UPARROW)
  10679.         , LoadCursor(NULL, IDC_WAIT)}; // If IDC_HAND were added, it would break existing scripts that rely on Unknown being synonymous with Hand.  If ever added, IDC_HAND should return NULL on Win95/NT.
  10680.     // The order in the below array must correspond to the order in the above array:
  10681.     static char *sCursorName[] = {"AppStarting", "Arrow"
  10682.         , "Cross", "Help", "IBeam"
  10683.         , "Icon", "No", "Size"
  10684.         , "SizeAll", "SizeNESW", "SizeNS"  // NESW = NorthEast+SouthWest
  10685.         , "SizeNWSE", "SizeWE", "UpArrow"
  10686.         , "Wait", CURSOR_UNKNOWN};  // The last item is used to mark end-of-array.
  10687.     static int cursor_count = sizeof(sCursor) / sizeof(HCURSOR);
  10688.  
  10689.     int i;
  10690.     for (i = 0; i < cursor_count; ++i)
  10691.         if (sCursor[i] == current_cursor)
  10692.             break;
  10693.  
  10694.     strlcpy(aBuf, sCursorName[i], SMALL_STRING_LENGTH + 1);  // If a is out-of-bounds, "Unknown" will be used.
  10695.     return (VarSizeType)strlen(aBuf);
  10696. }
  10697.  
  10698. VarSizeType BIV_ScreenWidth_Height(char *aBuf, char *aVarName)
  10699. {
  10700.     return aBuf
  10701.         ? (VarSizeType)strlen(ITOA(GetSystemMetrics(aVarName[13] ? SM_CYSCREEN : SM_CXSCREEN), aBuf))
  10702.         : MAX_NUMBER_LENGTH;
  10703. }
  10704.  
  10705. VarSizeType BIV_ScriptName(char *aBuf, char *aVarName)
  10706. {
  10707.     if (aBuf)
  10708.         strcpy(aBuf, g_script.mFileName);
  10709.     return (VarSizeType)strlen(g_script.mFileName);
  10710. }
  10711.  
  10712. VarSizeType BIV_ScriptDir(char *aBuf, char *aVarName)
  10713. {
  10714.     // v1.0.42.06: This function has been fixed not to call the following when we're called with aBuf!=NULL:
  10715.     // strlcpy(target_buf, g_script.mFileDir, MAX_PATH);
  10716.     // The above could crash because strlcpy() calls strncpy(), which zero fills the tail of the destination
  10717.     // up through the limit of its capacity.  But we might have returned an estimate less than MAX_PATH
  10718.     // when the caller called us the first time, which usually means that aBuf is smaller than MAX_PATH.
  10719.     if (!aBuf)
  10720.         return (VarSizeType)strlen(g_script.mFileDir) + 1; // +1 for conservative estimate in case g_script.mIsAutoIt2 (see below).
  10721.     // Otherwise, write the result to the buffer and return its exact length, not an estimate:
  10722.     size_t length = strlen(strcpy(aBuf, g_script.mFileDir)); // Caller has ensured that aBuf is large enough.
  10723.     // If it doesn't already have a final backslash, namely due to it being a root directory,
  10724.     // provide one so that it is backward compatible with AutoIt v2:
  10725.     if (g_script.mIsAutoIt2 && length && aBuf[length - 1] != '\\')
  10726.     {
  10727.         aBuf[length++] = '\\';
  10728.         aBuf[length] = '\0';
  10729.     }
  10730.     return (VarSizeType)length;
  10731. }
  10732.  
  10733. VarSizeType BIV_ScriptFullPath(char *aBuf, char *aVarName)
  10734. {
  10735.     return aBuf
  10736.         ? sprintf(aBuf, "%s\\%s", g_script.mFileDir, g_script.mFileName)
  10737.         :(VarSizeType)(strlen(g_script.mFileDir) + strlen(g_script.mFileName) + 1);
  10738. }
  10739.  
  10740. VarSizeType BIV_LineNumber(char *aBuf, char *aVarName)
  10741. // Caller has ensured that g_script.mCurrLine is not NULL.
  10742. {
  10743.     return aBuf
  10744.         ? (VarSizeType)strlen(ITOA(g_script.mCurrLine->mLineNumber, aBuf))
  10745.         : MAX_NUMBER_LENGTH;
  10746. }
  10747.  
  10748. VarSizeType BIV_LineFile(char *aBuf, char *aVarName)
  10749. // Caller has ensured that g_script.mCurrLine is not NULL.
  10750. {
  10751.     if (aBuf)
  10752.         strcpy(aBuf, Line::sSourceFile[g_script.mCurrLine->mFileIndex]);
  10753.     return (VarSizeType)strlen(Line::sSourceFile[g_script.mCurrLine->mFileIndex]);
  10754. }
  10755.  
  10756.  
  10757.  
  10758. VarSizeType BIV_LoopFileName(char *aBuf, char *aVarName) // Called by multiple callers.
  10759. {
  10760.     char *naked_filename;
  10761.     if (g.mLoopFile)
  10762.     {
  10763.         // The loop handler already prepended the script's directory in here for us:
  10764.         if (naked_filename = strrchr(g.mLoopFile->cFileName, '\\'))
  10765.             ++naked_filename;
  10766.         else // No backslash, so just make it the entire file name.
  10767.             naked_filename = g.mLoopFile->cFileName;
  10768.     }
  10769.     else
  10770.         naked_filename = "";
  10771.     if (aBuf)
  10772.         strcpy(aBuf, naked_filename);
  10773.     return (VarSizeType)strlen(naked_filename);
  10774. }
  10775.  
  10776. VarSizeType BIV_LoopFileShortName(char *aBuf, char *aVarName)
  10777. {
  10778.     char *short_filename = "";  // Set default.
  10779.     if (g.mLoopFile)
  10780.     {
  10781.         if (   !*(short_filename = g.mLoopFile->cAlternateFileName)   )
  10782.             // Files whose long name is shorter than the 8.3 usually don't have value stored here,
  10783.             // so use the long name whenever a short name is unavailable for any reason (could
  10784.             // also happen if NTFS has short-name generation disabled?)
  10785.             return BIV_LoopFileName(aBuf, "");
  10786.     }
  10787.     if (aBuf)
  10788.         strcpy(aBuf, short_filename);
  10789.     return (VarSizeType)strlen(short_filename);
  10790. }
  10791.  
  10792. VarSizeType BIV_LoopFileExt(char *aBuf, char *aVarName)
  10793. {
  10794.     char *file_ext = "";  // Set default.
  10795.     if (g.mLoopFile)
  10796.     {
  10797.         // The loop handler already prepended the script's directory in here for us:
  10798.         if (file_ext = strrchr(g.mLoopFile->cFileName, '.'))
  10799.             ++file_ext;
  10800.         else // Reset to empty string vs. NULL.
  10801.             file_ext = "";
  10802.     }
  10803.     if (aBuf)
  10804.         strcpy(aBuf, file_ext);
  10805.     return (VarSizeType)strlen(file_ext);
  10806. }
  10807.  
  10808. VarSizeType BIV_LoopFileDir(char *aBuf, char *aVarName)
  10809. {
  10810.     char *file_dir = "";  // Set default.
  10811.     char *last_backslash = NULL;
  10812.     if (g.mLoopFile)
  10813.     {
  10814.         // The loop handler already prepended the script's directory in here for us.
  10815.         // But if the loop had a relative path in its FilePattern, there might be
  10816.         // only a relative directory here, or no directory at all if the current
  10817.         // file is in the origin/root dir of the search:
  10818.         if (last_backslash = strrchr(g.mLoopFile->cFileName, '\\'))
  10819.         {
  10820.             *last_backslash = '\0'; // Temporarily terminate.
  10821.             file_dir = g.mLoopFile->cFileName;
  10822.         }
  10823.         else // No backslash, so there is no directory in this case.
  10824.             file_dir = "";
  10825.     }
  10826.     VarSizeType length = (VarSizeType)strlen(file_dir);
  10827.     if (!aBuf)
  10828.     {
  10829.         if (last_backslash)
  10830.             *last_backslash = '\\';  // Restore the orginal value.
  10831.         return length;
  10832.     }
  10833.     strcpy(aBuf, file_dir);
  10834.     if (last_backslash)
  10835.         *last_backslash = '\\';  // Restore the orginal value.
  10836.     return length;
  10837. }
  10838.  
  10839. VarSizeType BIV_LoopFileFullPath(char *aBuf, char *aVarName)
  10840. {
  10841.     // The loop handler already prepended the script's directory in cFileName for us:
  10842.     char *full_path = g.mLoopFile ? g.mLoopFile->cFileName : "";
  10843.     if (aBuf)
  10844.         strcpy(aBuf, full_path);
  10845.     return (VarSizeType)strlen(full_path);
  10846. }
  10847.  
  10848. VarSizeType BIV_LoopFileLongPath(char *aBuf, char *aVarName)
  10849. {
  10850.     char *unused, buf[MAX_PATH] = ""; // Set default.
  10851.     if (g.mLoopFile)
  10852.     {
  10853.         // GetFullPathName() is done in addition to ConvertFilespecToCorrectCase() for the following reasons:
  10854.         // 1) It's currrently the only easy way to get the full path of the directory in which a file resides.
  10855.         //    For example, if a script is passed a filename via command line parameter, that file could be
  10856.         //    either an absolute path or a relative path.  If relative, of course it's relative to A_WorkingDir.
  10857.         //    The problem is, the script would have to manually detect this, which would probably take several
  10858.         //    extra steps.
  10859.         // 2) A_LoopFileLongPath is mostly intended for the following cases, and in all of them it seems
  10860.         //    preferable to have the full/absolute path rather than the relative path:
  10861.         //    a) Files dragged onto a .ahk script when the drag-and-drop option has been enabled via the Installer.
  10862.         //    b) Files passed into the script via command line.
  10863.         // The below also serves to make a copy because changing the original would yield
  10864.         // unexpected/inconsistent results in a script that retrieves the A_LoopFileFullPath
  10865.         // but only conditionally retrieves A_LoopFileLongPath.
  10866.         if (!GetFullPathName(g.mLoopFile->cFileName, MAX_PATH, buf, &unused))
  10867.             *buf = '\0'; // It might fail if NtfsDisable8dot3NameCreation is turned on in the registry, and possibly for other reasons.
  10868.         else
  10869.             // The below is called in case the loop is being used to convert filename specs that were passed
  10870.             // in from the command line, which thus might not be the proper case (at least in the path
  10871.             // portion of the filespec), as shown in the file system:
  10872.             ConvertFilespecToCorrectCase(buf);
  10873.     }
  10874.     if (aBuf)
  10875.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string). This is true for ReadRegString()'s API call and may be true for other API calls like the one here.
  10876.     return (VarSizeType)strlen(buf); // Must explicitly calculate the length rather than using the return value from GetFullPathName(), because ConvertFilespecToCorrectCase() expands 8.3 path components.
  10877. }
  10878.  
  10879. VarSizeType BIV_LoopFileShortPath(char *aBuf, char *aVarName)
  10880. // Unlike GetLoopFileShortName(), this function returns blank when there is no short path.
  10881. // This is done so that there's a way for the script to more easily tell the difference between
  10882. // an 8.3 name not being available (due to the being disabled in the registry) and the short
  10883. // name simply being the same as the long name.  For example, if short name creation is disabled
  10884. // in the registry, A_LoopFileShortName would contain the long name instead, as documented.
  10885. // But to detect if that short name is really a long name, A_LoopFileShortPath could be checked
  10886. // and if it's blank, there is no short name available.
  10887. {
  10888.     char buf[MAX_PATH] = ""; // Set default.
  10889.     DWORD length = 0;        //
  10890.     if (g.mLoopFile)
  10891.         // The loop handler already prepended the script's directory in cFileName for us:
  10892.         if (   !(length = GetShortPathName(g.mLoopFile->cFileName, buf, MAX_PATH))   )
  10893.             *buf = '\0'; // It might fail if NtfsDisable8dot3NameCreation is turned on in the registry, and possibly for other reasons.
  10894.     if (aBuf)
  10895.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that (even though it's large enough to hold the string). This is true for ReadRegString()'s API call and may be true for other API calls like the one here.
  10896.     return (VarSizeType)length;
  10897. }
  10898.  
  10899. VarSizeType BIV_LoopFileTime(char *aBuf, char *aVarName)
  10900. {
  10901.     char buf[64];
  10902.     char *target_buf = aBuf ? aBuf : buf;
  10903.     *target_buf = '\0'; // Set default.
  10904.     if (g.mLoopFile)
  10905.     {
  10906.         FILETIME ft;
  10907.         switch(toupper(aVarName[14])) // A_LoopFileTime[A]ccessed
  10908.         {
  10909.         case 'M': ft = g.mLoopFile->ftLastWriteTime; break;
  10910.         case 'C': ft = g.mLoopFile->ftCreationTime; break;
  10911.         default: ft = g.mLoopFile->ftLastAccessTime;
  10912.         }
  10913.         FileTimeToYYYYMMDD(target_buf, ft, true);
  10914.     }
  10915.     return (VarSizeType)strlen(target_buf);
  10916. }
  10917.  
  10918. VarSizeType BIV_LoopFileAttrib(char *aBuf, char *aVarName)
  10919. {
  10920.     char buf[64];
  10921.     char *target_buf = aBuf ? aBuf : buf;
  10922.     *target_buf = '\0'; // Set default.
  10923.     if (g.mLoopFile)
  10924.         FileAttribToStr(target_buf, g.mLoopFile->dwFileAttributes);
  10925.     return (VarSizeType)strlen(target_buf);
  10926. }
  10927.  
  10928. VarSizeType BIV_LoopFileSize(char *aBuf, char *aVarName)
  10929. {
  10930.     // Don't use MAX_NUMBER_LENGTH in case user has selected a very long float format via SetFormat.
  10931.     char str[128];
  10932.     char *target_buf = aBuf ? aBuf : str;
  10933.     *target_buf = '\0';  // Set default.
  10934.     if (g.mLoopFile)
  10935.     {
  10936.  
  10937.         // UPDATE: 64-bit ints are now standard, so the following is obsolete:
  10938.         // It's a documented limitation that the size will show as negative if
  10939.         // greater than 2 gig, and will be wrong if greater than 4 gig.  For files
  10940.         // that large, scripts should use the KB version of this function instead.
  10941.         // If a file is over 4gig, set the value to be the maximum size (-1 when
  10942.         // expressed as a signed integer, since script variables are based entirely
  10943.         // on 32-bit signed integers due to the use of ATOI(), etc.).
  10944.         //sprintf(str, "%d%", g.mLoopFile->nFileSizeHigh ? -1 : (int)g.mLoopFile->nFileSizeLow);
  10945.         ULARGE_INTEGER ul;
  10946.         ul.HighPart = g.mLoopFile->nFileSizeHigh;
  10947.         ul.LowPart = g.mLoopFile->nFileSizeLow;
  10948.         int divider;
  10949.         switch (toupper(aVarName[14])) // A_LoopFileSize[K/M]B
  10950.         {
  10951.         case 'K': divider = 1024; break;
  10952.         case 'M': divider = 1024*1024; break;
  10953.         default:  divider = 0;
  10954.         }
  10955.         ITOA64((__int64)(divider ? ((unsigned __int64)ul.QuadPart / divider) : ul.QuadPart), target_buf);
  10956.     }
  10957.     return (VarSizeType)strlen(target_buf);
  10958. }
  10959.  
  10960. VarSizeType BIV_LoopRegType(char *aBuf, char *aVarName)
  10961. {
  10962.     char buf[MAX_PATH] = ""; // Set default.
  10963.     if (g.mLoopRegItem)
  10964.         Line::RegConvertValueType(buf, MAX_PATH, g.mLoopRegItem->type);
  10965.     if (aBuf)
  10966.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that due to the zero-the-unused-part behavior of strlcpy/strncpy.
  10967.     return (VarSizeType)strlen(buf);
  10968. }
  10969.  
  10970. VarSizeType BIV_LoopRegKey(char *aBuf, char *aVarName)
  10971. {
  10972.     char buf[MAX_PATH] = ""; // Set default.
  10973.     if (g.mLoopRegItem)
  10974.         // Use root_key_type, not root_key (which might be a remote vs. local HKEY):
  10975.         Line::RegConvertRootKey(buf, MAX_PATH, g.mLoopRegItem->root_key_type);
  10976.     if (aBuf)
  10977.         strcpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for aBuf can crash when aBuf is actually smaller than that due to the zero-the-unused-part behavior of strlcpy/strncpy.
  10978.     return (VarSizeType)strlen(buf);
  10979. }
  10980.  
  10981. VarSizeType BIV_LoopRegSubKey(char *aBuf, char *aVarName)
  10982. {
  10983.     char *str = g.mLoopRegItem ? g.mLoopRegItem->subkey : "";
  10984.     if (aBuf)
  10985.         strcpy(aBuf, str);
  10986.     return (VarSizeType)strlen(str);
  10987. }
  10988.  
  10989. VarSizeType BIV_LoopRegName(char *aBuf, char *aVarName)
  10990. {
  10991.     // This can be either the name of a subkey or the name of a value.
  10992.     char *str = g.mLoopRegItem ? g.mLoopRegItem->name : "";
  10993.     if (aBuf)
  10994.         strcpy(aBuf, str);
  10995.     return (VarSizeType)strlen(str);
  10996. }
  10997.  
  10998. VarSizeType BIV_LoopRegTimeModified(char *aBuf, char *aVarName)
  10999. {
  11000.     char buf[64];
  11001.     char *target_buf = aBuf ? aBuf : buf;
  11002.     *target_buf = '\0'; // Set default.
  11003.     // Only subkeys (not values) have a time.  In addition, Win9x doesn't support retrieval
  11004.     // of the time (nor does it store it), so make the var blank in that case:
  11005.     if (g.mLoopRegItem && g.mLoopRegItem->type == REG_SUBKEY && !g_os.IsWin9x())
  11006.         FileTimeToYYYYMMDD(target_buf, g.mLoopRegItem->ftLastWriteTime, true);
  11007.     return (VarSizeType)strlen(target_buf);
  11008. }
  11009.  
  11010. VarSizeType BIV_LoopReadLine(char *aBuf, char *aVarName)
  11011. {
  11012.     char *str = g.mLoopReadFile ? g.mLoopReadFile->mCurrentLine : "";
  11013.     if (aBuf)
  11014.         strcpy(aBuf, str);
  11015.     return (VarSizeType)strlen(str);
  11016. }
  11017.  
  11018. VarSizeType BIV_LoopField(char *aBuf, char *aVarName)
  11019. {
  11020.     char *str = g.mLoopField ? g.mLoopField : "";
  11021.     if (aBuf)
  11022.         strcpy(aBuf, str);
  11023.     return (VarSizeType)strlen(str);
  11024. }
  11025.  
  11026. VarSizeType BIV_LoopIndex(char *aBuf, char *aVarName)
  11027. {
  11028.     return aBuf
  11029.         ? (VarSizeType)strlen(ITOA64(g.mLoopIteration, aBuf)) // Must return exact length when aBuf isn't NULL.
  11030.         : MAX_NUMBER_LENGTH; // Probably performs better to return a conservative estimate for the first pass than to call ITOA64 for both passes.
  11031. }
  11032.  
  11033.  
  11034.  
  11035. VarSizeType BIV_ThisFunc(char *aBuf, char *aVarName)
  11036. {
  11037.     char *name = g.CurrentFunc ? g.CurrentFunc->mName : "";
  11038.     if (aBuf)
  11039.         strcpy(aBuf, name);
  11040.     return (VarSizeType)strlen(name);
  11041. }
  11042.  
  11043. VarSizeType BIV_ThisLabel(char *aBuf, char *aVarName)
  11044. {
  11045.     char *name = g.CurrentLabel ? g.CurrentLabel->mName : "";
  11046.     if (aBuf)
  11047.         strcpy(aBuf, name);
  11048.     return (VarSizeType)strlen(name);
  11049. }
  11050.  
  11051. VarSizeType BIV_ThisMenuItem(char *aBuf, char *aVarName)
  11052. {
  11053.     if (aBuf)
  11054.         strcpy(aBuf, g_script.mThisMenuItemName);
  11055.     return (VarSizeType)strlen(g_script.mThisMenuItemName);
  11056. }
  11057.  
  11058. VarSizeType BIV_ThisMenuItemPos(char *aBuf, char *aVarName)
  11059. {
  11060.     if (!aBuf) // To avoid doing possibly high-overhead calls twice, merely return a conservative estimate for the first pass.
  11061.         return MAX_NUMBER_LENGTH;
  11062.     // The menu item's position is discovered through this process -- rather than doing
  11063.     // something higher performance such as storing the menu handle or pointer to menu/item
  11064.     // object in g_script -- because those things tend to be volatile.  For example, a menu
  11065.     // or menu item object might be destroyed between the time the user selects it and the
  11066.     // time this variable is referenced in the script.  Thus, by definition, this variable
  11067.     // contains the CURRENT position of the most recently selected menu item within its
  11068.     // CURRENT menu.
  11069.     if (*g_script.mThisMenuName && *g_script.mThisMenuItemName)
  11070.     {
  11071.         UserMenu *menu = g_script.FindMenu(g_script.mThisMenuName);
  11072.         if (menu)
  11073.         {
  11074.             // If the menu does not physically exist yet (perhaps due to being destroyed as a result
  11075.             // of DeleteAll, Delete, or some other operation), create it so that the position of the
  11076.             // item can be determined.  This is done for consistency in behavior.
  11077.             if (!menu->mMenu)
  11078.                 menu->Create();
  11079.             UINT menu_item_pos = menu->GetItemPos(g_script.mThisMenuItemName);
  11080.             if (menu_item_pos < UINT_MAX) // Success
  11081.                 return (VarSizeType)strlen(UTOA(menu_item_pos + 1, aBuf)); // +1 to convert from zero-based to 1-based.
  11082.         }
  11083.     }
  11084.     // Otherwise:
  11085.     *aBuf = '\0';
  11086.     return 0;
  11087. }
  11088.  
  11089. VarSizeType BIV_ThisMenu(char *aBuf, char *aVarName)
  11090. {
  11091.     if (aBuf)
  11092.         strcpy(aBuf, g_script.mThisMenuName);
  11093.     return (VarSizeType)strlen(g_script.mThisMenuName);
  11094. }
  11095.  
  11096. VarSizeType BIV_ThisHotkey(char *aBuf, char *aVarName)
  11097. {
  11098.     if (aBuf)
  11099.         strcpy(aBuf, g_script.mThisHotkeyName);
  11100.     return (VarSizeType)strlen(g_script.mThisHotkeyName);
  11101. }
  11102.  
  11103. VarSizeType BIV_PriorHotkey(char *aBuf, char *aVarName)
  11104. {
  11105.     if (aBuf)
  11106.         strcpy(aBuf, g_script.mPriorHotkeyName);
  11107.     return (VarSizeType)strlen(g_script.mPriorHotkeyName);
  11108. }
  11109.  
  11110. VarSizeType BIV_TimeSinceThisHotkey(char *aBuf, char *aVarName)
  11111. {
  11112.     if (!aBuf) // IMPORTANT: Conservative estimate because the time might change between 1st & 2nd calls.
  11113.         return MAX_NUMBER_LENGTH;
  11114.     // It must be the type of hotkey that has a label because we want the TimeSinceThisHotkey
  11115.     // value to be "in sync" with the value of ThisHotkey itself (i.e. use the same method
  11116.     // to determine which hotkey is the "this" hotkey):
  11117.     if (*g_script.mThisHotkeyName)
  11118.         // Even if GetTickCount()'s TickCount has wrapped around to zero and the timestamp hasn't,
  11119.         // DWORD math still gives the right answer as long as the number of days between
  11120.         // isn't greater than about 49.  See MyGetTickCount() for explanation of %d vs. %u.
  11121.         // Update: Using 64-bit ints now, so above is obsolete:
  11122.         //snprintf(str, sizeof(str), "%d", (DWORD)(GetTickCount() - g_script.mThisHotkeyStartTime));
  11123.         ITOA64((__int64)(GetTickCount() - g_script.mThisHotkeyStartTime), aBuf);
  11124.     else
  11125.         strcpy(aBuf, "-1");
  11126.     return (VarSizeType)strlen(aBuf);
  11127. }
  11128.  
  11129. VarSizeType BIV_TimeSincePriorHotkey(char *aBuf, char *aVarName)
  11130. {
  11131.     if (!aBuf) // IMPORTANT: Conservative estimate because the time might change between 1st & 2nd calls.
  11132.         return MAX_NUMBER_LENGTH;
  11133.     if (*g_script.mPriorHotkeyName)
  11134.         // See MyGetTickCount() for explanation for explanation:
  11135.         //snprintf(str, sizeof(str), "%d", (DWORD)(GetTickCount() - g_script.mPriorHotkeyStartTime));
  11136.         ITOA64((__int64)(GetTickCount() - g_script.mPriorHotkeyStartTime), aBuf);
  11137.     else
  11138.         strcpy(aBuf, "-1");
  11139.     return (VarSizeType)strlen(aBuf);
  11140. }
  11141.  
  11142. VarSizeType BIV_EndChar(char *aBuf, char *aVarName)
  11143. {
  11144.     if (aBuf)
  11145.     {
  11146.         *aBuf++ = g_script.mEndChar;
  11147.         *aBuf = '\0';
  11148.     }
  11149.     return 1;
  11150. }
  11151.  
  11152.  
  11153.  
  11154. VarSizeType BIV_Gui(char *aBuf, char *aVarName)
  11155. // We're returning the length of the var's contents, not the size.
  11156. {
  11157.     char buf[MAX_NUMBER_SIZE];
  11158.     char *target_buf = aBuf ? aBuf : buf;
  11159.  
  11160.     if (g.GuiWindowIndex >= MAX_GUI_WINDOWS) // The current thread was not launched as a result of GUI action.
  11161.     {
  11162.         *target_buf = '\0';
  11163.         return 0;
  11164.     }
  11165.  
  11166.     switch (toupper(aVarName[5]))
  11167.     {
  11168.     case 'W':
  11169.         // g.GuiPoint.x was overloaded to contain the size, since there are currently never any cases when
  11170.         // A_GuiX/Y and A_GuiWidth/Height are both valid simultaneously.  It is documented that each of these
  11171.         // variables is defined only in proper types of subroutines.
  11172.         _itoa(LOWORD(g.GuiPoint.x), target_buf, 10);
  11173.         // Above is always stored as decimal vs. hex, regardless of script settings.
  11174.         break;
  11175.     case 'H':
  11176.         _itoa(HIWORD(g.GuiPoint.x), target_buf, 10); // See comments above.
  11177.         break;
  11178.     case 'X':
  11179.         _itoa(g.GuiPoint.x, target_buf, 10);
  11180.         break;
  11181.     case 'Y':
  11182.         _itoa(g.GuiPoint.y, target_buf, 10);
  11183.         break;
  11184.     case '\0': // A_Gui
  11185.         _itoa(g.GuiWindowIndex + 1, target_buf, 10);  // Always stored as decimal vs. hex, regardless of script settings.
  11186.         break;
  11187.     }
  11188.  
  11189.     return (VarSizeType)strlen(target_buf);
  11190. }
  11191.  
  11192.  
  11193.  
  11194. VarSizeType BIV_GuiControl(char *aBuf, char *aVarName)
  11195. {
  11196.     // Other logic ensures that g.GuiControlIndex is out-of-bounds whenever g.GuiWindowIndex is.
  11197.     // That is why g.GuiWindowIndex is not checked to make sure it's less than MAX_GUI_WINDOWS.
  11198.     return GuiType::ControlGetName(g.GuiWindowIndex, g.GuiControlIndex, aBuf);
  11199. }
  11200.  
  11201.  
  11202.  
  11203. VarSizeType BIV_GuiEvent(char *aBuf, char *aVarName)
  11204. // We're returning the length of the var's contents, not the size.
  11205. {
  11206.     if (g.GuiEvent == GUI_EVENT_DROPFILES)
  11207.     {
  11208.         GuiType *pgui;
  11209.         UINT u, file_count;
  11210.         // GUI_EVENT_DROPFILES should mean that g.GuiWindowIndex < MAX_GUI_WINDOWS, but the below will double check
  11211.         // that in case g.GuiEvent can ever be set to that value as a result of receiving a bogus message in the queue.
  11212.         if (g.GuiWindowIndex >= MAX_GUI_WINDOWS  // The current thread was not launched as a result of GUI action or this is a bogus msg.
  11213.             || !(pgui = g_gui[g.GuiWindowIndex]) // Gui window no longer exists.  Relies on short-circuit boolean.
  11214.             || !pgui->mHdrop // No HDROP (probably impossible unless g.GuiEvent was given a bogus value somehow).
  11215.             || !(file_count = DragQueryFile(pgui->mHdrop, 0xFFFFFFFF, NULL, 0))) // No files in the drop (not sure if this is possible).
  11216.             // All of the above rely on short-circuit boolean order.
  11217.         {
  11218.             // Make the dropped-files list blank since there is no HDROP to query (or no files in it).
  11219.             if (aBuf)
  11220.                 *aBuf = '\0';
  11221.             return 0;
  11222.         }
  11223.         // Above has ensured that file_count > 0
  11224.         if (aBuf)
  11225.         {
  11226.             char buf[MAX_PATH], *cp = aBuf;
  11227.             UINT length;
  11228.             for (u = 0; u < file_count; ++u)
  11229.             {
  11230.                 length = DragQueryFile(pgui->mHdrop, u, buf, MAX_PATH); // MAX_PATH is arbitrary since aBuf is already known to be large enough.
  11231.                 strcpy(cp, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for something that isn't actually that large (though clearly large enoug) due to previous size-estimation phase) can crash because the API may read/write data beyond what it actually needs.
  11232.                 cp += length;
  11233.                 if (u < file_count - 1) // i.e omit the LF after the last file to make parsing via "Loop, Parse" easier.
  11234.                     *cp++ = '\n';
  11235.                 // Although the transcription of files on the clipboard into their text filenames is done
  11236.                 // with \r\n (so that they're in the right format to be pasted to other apps as a plain text
  11237.                 // list), it seems best to use a plain linefeed for dropped files since they won't be going
  11238.                 // onto the clipboard nearly as often, and `n is easier to parse.  Also, a script array isn't
  11239.                 // used because large file lists would then consume a lot more of memory because arrays
  11240.                 // are permanent once created, and also there would be wasted space due to the part of each
  11241.                 // variable's capacity not used by the filename.
  11242.             }
  11243.             // No need for final termination of string because the last item lacks a newline.
  11244.             return (VarSizeType)(cp - aBuf); // This is the length of what's in the buffer.
  11245.         }
  11246.         else
  11247.         {
  11248.             VarSizeType total_length = 0;
  11249.             for (u = 0; u < file_count; ++u)
  11250.                 total_length += DragQueryFile(pgui->mHdrop, u, NULL, 0);
  11251.                 // Above: MSDN: "If the lpszFile buffer address is NULL, the return value is the required size,
  11252.                 // in characters, of the buffer, not including the terminating null character."
  11253.             return total_length + file_count - 1; // Include space for a linefeed after each filename except the last.
  11254.         }
  11255.         // Don't call DragFinish() because this variable might be referred to again before this thread
  11256.         // is done.  DragFinish() is called by MsgSleep() when the current thread finishes.
  11257.     }
  11258.  
  11259.     // Otherwise, this event is not GUI_EVENT_DROPFILES, so use standard modes of operation.
  11260.     static char *sNames[] = GUI_EVENT_NAMES;
  11261.     if (!aBuf)
  11262.         return (g.GuiEvent < GUI_EVENT_FIRST_UNNAMED) ? (VarSizeType)strlen(sNames[g.GuiEvent]) : 1;
  11263.     // Otherwise:
  11264.     if (g.GuiEvent < GUI_EVENT_FIRST_UNNAMED)
  11265.         return (VarSizeType)strlen(strcpy(aBuf, sNames[g.GuiEvent]));
  11266.     else // g.GuiEvent is assumed to be an ASCII value, such as a digit.  This supports Slider controls.
  11267.     {
  11268.         *aBuf++ = (char)(UCHAR)g.GuiEvent;
  11269.         *aBuf = '\0';
  11270.         return 1;
  11271.     }
  11272. }
  11273.  
  11274.  
  11275.  
  11276. VarSizeType BIV_EventInfo(char *aBuf, char *aVarName)
  11277. // We're returning the length of the var's contents, not the size.
  11278. {
  11279.     return aBuf
  11280.         ? (VarSizeType)strlen(UTOA(g.EventInfo, aBuf)) // Must return exact length when aBuf isn't NULL.
  11281.         : MAX_NUMBER_LENGTH;
  11282. }
  11283.  
  11284.  
  11285.  
  11286. VarSizeType BIV_TimeIdle(char *aBuf, char *aVarName) // Called by multiple callers.
  11287. {
  11288.     if (!aBuf) // IMPORTANT: Conservative estimate because tick might change between 1st & 2nd calls.
  11289.         return MAX_NUMBER_LENGTH;
  11290.     *aBuf = '\0';  // Set default.
  11291.     if (g_os.IsWin2000orLater()) // Checked in case the function is present in the OS but "not implemented".
  11292.     {
  11293.         // Must fetch it at runtime, otherwise the program can't even be launched on Win9x/NT:
  11294.         typedef BOOL (WINAPI *MyGetLastInputInfoType)(PLASTINPUTINFO);
  11295.         static MyGetLastInputInfoType MyGetLastInputInfo = (MyGetLastInputInfoType)
  11296.             GetProcAddress(GetModuleHandle("user32"), "GetLastInputInfo");
  11297.         if (MyGetLastInputInfo)
  11298.         {
  11299.             LASTINPUTINFO lii;
  11300.             lii.cbSize = sizeof(lii);
  11301.             if (MyGetLastInputInfo(&lii))
  11302.                 ITOA64(GetTickCount() - lii.dwTime, aBuf);
  11303.         }
  11304.     }
  11305.     return (VarSizeType)strlen(aBuf);
  11306. }
  11307.  
  11308.  
  11309.  
  11310. VarSizeType BIV_TimeIdlePhysical(char *aBuf, char *aVarName)
  11311. // This is here rather than in script.h with the others because it depends on
  11312. // hotkey.h and globaldata.h, which can't be easily included in script.h due to
  11313. // mutual dependency issues.
  11314. {
  11315.     // If neither hook is active, default this to the same as the regular idle time:
  11316.     if (!(g_KeybdHook || g_MouseHook))
  11317.         return BIV_TimeIdle(aBuf, "");
  11318.     if (!aBuf)
  11319.         return MAX_NUMBER_LENGTH; // IMPORTANT: Conservative estimate because tick might change between 1st & 2nd calls.
  11320.     return (VarSizeType)strlen(ITOA64(GetTickCount() - g_TimeLastInputPhysical, aBuf)); // Switching keyboard layouts/languages sometimes sees to throw off the timestamps of the incoming events in the hook.
  11321. }
  11322.  
  11323.  
  11324. ////////////////////////
  11325. // BUILT-IN FUNCTIONS //
  11326. ////////////////////////
  11327.  
  11328. // Interface for DynaCall():
  11329. #define  DC_MICROSOFT           0x0000      // Default
  11330. #define  DC_BORLAND             0x0001      // Borland compat
  11331. #define  DC_CALL_CDECL          0x0010      // __cdecl
  11332. #define  DC_CALL_STD            0x0020      // __stdcall
  11333. #define  DC_RETVAL_MATH4        0x0100      // Return value in ST
  11334. #define  DC_RETVAL_MATH8        0x0200      // Return value in ST
  11335.  
  11336. #define  DC_CALL_STD_BO         (DC_CALL_STD | DC_BORLAND)
  11337. #define  DC_CALL_STD_MS         (DC_CALL_STD | DC_MICROSOFT)
  11338. #define  DC_CALL_STD_M8         (DC_CALL_STD | DC_RETVAL_MATH8)
  11339.  
  11340. union DYNARESULT                // Various result types
  11341. {      
  11342.     int     Int;                // Generic four-byte type
  11343.     long    Long;               // Four-byte long
  11344.     void   *Pointer;            // 32-bit pointer
  11345.     float   Float;              // Four byte real
  11346.     double  Double;             // 8-byte real
  11347.     __int64 Int64;              // big int (64-bit)
  11348. };
  11349.  
  11350. struct DYNAPARM
  11351. {
  11352.     union
  11353.     {
  11354.         int value_int; // Args whose width is less than 32-bit are also put in here because they are right justified within a 32-bit block on the stack.
  11355.         float value_float;
  11356.         __int64 value_int64;
  11357.         double value_double;
  11358.         char *str;
  11359.     };
  11360.     // Might help reduce struct size to keep other members last and adjacent to each other (due to
  11361.     // 8-byte alignment caused by the presence of double and __int64 members in the union above).
  11362.     DllArgTypes type;
  11363.     bool passed_by_address;
  11364.     bool is_unsigned; // Allows return value and output parameters to be interpreted as unsigned vs. signed.
  11365. };
  11366.  
  11367.  
  11368.  
  11369. DYNARESULT DynaCall(int aFlags, void *aFunction, DYNAPARM aParam[], int aParamCount, DWORD &aException
  11370.     , void *aRet, int aRetSize)
  11371. // Based on the code by Ton Plooy <tonp@xs4all.nl>.
  11372. // Call the specified function with the given parameters. Build a proper stack and take care of correct
  11373. // return value processing.
  11374. {
  11375.     aException = 0;  // Set default output parameter for caller.
  11376.     SetLastError(g.LastError); // v1.0.46.07: In case the function about to be called doesn't change last-error, this line serves to retain the script's previous last-error rather than some arbitrary one produced by AutoHotkey's own internal API calls.  This line has no measurable impact on performance.
  11377.  
  11378.     // Declaring all variables early should help minimize stack interference of C code with asm.
  11379.     DWORD *our_stack;
  11380.     int param_size;
  11381.     DWORD stack_dword, our_stack_size = 0; // Both might have to be DWORD for _asm.
  11382.     BYTE *cp;
  11383.     DYNARESULT Res = {0}; // This struct is to be returned to caller by value.
  11384.     DWORD esp_start, esp_end, dwEAX, dwEDX;
  11385.     int i, esp_delta; // Declare this here rather than later to prevent C code from interfering with esp.
  11386.  
  11387.     // Reserve enough space on the stack to handle the worst case of our args (which is currently a
  11388.     // maximum of 8 bytes per arg). This avoids any chance that compiler-generated code will use
  11389.     // the stack in a way that disrupts our insertion of args onto the stack.
  11390.     DWORD reserved_stack_size = aParamCount * 8;
  11391.     _asm
  11392.     {
  11393.         mov our_stack, esp  // our_stack is the location where we will write our args (bypassing "push").
  11394.         sub esp, reserved_stack_size  // The stack grows downward, so this "allocates" space on the stack.
  11395.     }
  11396.  
  11397.     // "Push" args onto the portion of the stack reserved above. Every argument is aligned on a 4-byte boundary.
  11398.     // We start at the rightmost argument (i.e. reverse order).
  11399.     for (i = aParamCount - 1; i > -1; --i)
  11400.     {
  11401.         DYNAPARM &this_param = aParam[i]; // For performance and convenience.
  11402.         // Push the arg or its address onto the portion of the stack that was reserved for our use above.
  11403.         if (this_param.passed_by_address)
  11404.         {
  11405.             stack_dword = (DWORD)(size_t)&this_param.value_int; // Any union member would work.
  11406.             --our_stack;              // ESP = ESP - 4
  11407.             *our_stack = stack_dword; // SS:[ESP] = stack_dword
  11408.             our_stack_size += 4;      // Keep track of how many bytes are on our reserved portion of the stack.
  11409.         }
  11410.         else // this_param's value is contained directly inside the union.
  11411.         {
  11412.             param_size = (this_param.type == DLL_ARG_INT64 || this_param.type == DLL_ARG_DOUBLE) ? 8 : 4;
  11413.             our_stack_size += param_size; // Must be done before our_stack_size is decremented below.  Keep track of how many bytes are on our reserved portion of the stack.
  11414.             cp = (BYTE *)&this_param.value_int + param_size - 4; // Start at the right side of the arg and work leftward.
  11415.             while (param_size > 0)
  11416.             {
  11417.                 stack_dword = *(DWORD *)cp;  // Get first four bytes
  11418.                 cp -= 4;                     // Next part of argument
  11419.                 --our_stack;                 // ESP = ESP - 4
  11420.                 *our_stack = stack_dword;    // SS:[ESP] = stack_dword
  11421.                 param_size -= 4;
  11422.             }
  11423.         }
  11424.     }
  11425.  
  11426.     if ((aRet != NULL) && ((aFlags & DC_BORLAND) || (aRetSize > 8)))
  11427.     {
  11428.         // Return value isn't passed through registers, memory copy
  11429.         // is performed instead. Pass the pointer as hidden arg.
  11430.         our_stack_size += 4;       // Add stack size
  11431.         --our_stack;               // ESP = ESP - 4
  11432.         *our_stack = (DWORD)(size_t)aRet;  // SS:[ESP] = pMem
  11433.     }
  11434.  
  11435.     // Call the function.
  11436.     __try // Each try/except section adds at most 240 bytes of uncompressed code, and typically doesn't measurably affect performance.
  11437.     {
  11438.         _asm
  11439.         {
  11440.             add esp, reserved_stack_size // Restore to original position
  11441.             mov esp_start, esp      // For detecting whether a DC_CALL_STD function was sent too many or too few args.
  11442.             sub esp, our_stack_size // Adjust ESP to indicate that the args have already been pushed onto the stack.
  11443.             call [aFunction]        // Stack is now properly built, we can call the function
  11444.         }
  11445.     }
  11446.     __except(EXCEPTION_EXECUTE_HANDLER)
  11447.     {
  11448.         aException = GetExceptionCode(); // aException is an output parameter for our caller.
  11449.     }
  11450.  
  11451.     // Even if an exception occurred (perhaps due to the callee having been passed a bad pointer),
  11452.     // attempt to restore the stack to prevent making things even worse.
  11453.     _asm
  11454.     {
  11455.         mov esp_end, esp        // See below.
  11456.         mov esp, esp_start      //
  11457.         // For DC_CALL_STD functions (since they pop their own arguments off the stack):
  11458.         // Since the stack grows downward in memory, if the value of esp after the call is less than
  11459.         // that before the call's args were pushed onto the stack, there are still items left over on
  11460.         // the stack, meaning that too many args (or an arg too large) were passed to the callee.
  11461.         // Conversely, if esp is now greater that it should be, too many args were popped off the
  11462.         // stack by the callee, meaning that too few args were provided to it.  In either case,
  11463.         // and even for CDECL, the following line restores esp to what it was before we pushed the
  11464.         // function's args onto the stack, which in the case of DC_CALL_STD helps prevent crashes
  11465.         // due too too many or to few args having been passed.
  11466.         mov dwEAX, eax          // Save eax/edx registers
  11467.         mov dwEDX, edx
  11468.     }
  11469.  
  11470.     // Possibly adjust stack and read return values.
  11471.     // The following is commented out because the stack (esp) is restored above, for both CDECL and STD.
  11472.     //if (aFlags & DC_CALL_CDECL)
  11473.     //    _asm add esp, our_stack_size    // CDECL requires us to restore the stack after the call.
  11474.     if (aFlags & DC_RETVAL_MATH4)
  11475.         _asm fstp dword ptr [Res]
  11476.     else if (aFlags & DC_RETVAL_MATH8)
  11477.         _asm fstp qword ptr [Res]
  11478.     else if (!aRet)
  11479.     {
  11480.         _asm
  11481.         {
  11482.             mov  eax, [dwEAX]
  11483.             mov  DWORD PTR [Res], eax
  11484.             mov  edx, [dwEDX]
  11485.             mov  DWORD PTR [Res + 4], edx
  11486.         }
  11487.     }
  11488.     else if (((aFlags & DC_BORLAND) == 0) && (aRetSize <= 8))
  11489.     {
  11490.         // Microsoft optimized less than 8-bytes structure passing
  11491.         _asm
  11492.         {
  11493.             mov ecx, DWORD PTR [aRet]
  11494.             mov eax, [dwEAX]
  11495.             mov DWORD PTR [ecx], eax
  11496.             mov edx, [dwEDX]
  11497.             mov DWORD PTR [ecx + 4], edx
  11498.         }
  11499.     }
  11500.  
  11501.     // v1.0.42.03: The following supports A_LastError. It's called even if an exception occurred because it
  11502.     // might add value in some such cases.  Benchmarks show that this has no measurable impact on performance.
  11503.     // A_LastError was implemented rather than trying to change things so that a script could use DllCall to
  11504.     // call GetLastError() because: Even if we could avoid calling any API function that resets LastError
  11505.     // (which seems unlikely) it would be difficult to maintain (and thus a source of bugs) as revisions are
  11506.     // made in the future.
  11507.     g.LastError = GetLastError();
  11508.  
  11509.     char buf[32];
  11510.     esp_delta = esp_start - esp_end; // Positive number means too many args were passed, negative means too few.
  11511.     if (esp_delta && (aFlags & DC_CALL_STD))
  11512.     {
  11513.         *buf = 'A'; // The 'A' prefix indicates the call was made, but with too many or too few args.
  11514.         _itoa(esp_delta, buf + 1, 10);
  11515.         g_ErrorLevel->Assign(buf); // Assign buf not _itoa()'s return value, which is the wrong location.
  11516.     }
  11517.     // Too many or too few args takes precedence over reporting the exception because it's more informative.
  11518.     // In other words, any exception was likely caused by the fact that there were too many or too few.
  11519.     else if (aException)
  11520.     {
  11521.         // It's a little easier to recongize the common error codes when they're in hex format.
  11522.         buf[0] = '0';
  11523.         buf[1] = 'x';
  11524.         _ultoa(aException, buf + 2, 16);
  11525.         g_ErrorLevel->Assign(buf); // Positive ErrorLevel numbers are reserved for exception codes.
  11526.     }
  11527.     else
  11528.         g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  11529.  
  11530.     return Res;
  11531. }
  11532.  
  11533.  
  11534.  
  11535. void ConvertDllArgType(char *aBuf[], DYNAPARM &aDynaParam)
  11536. // Helper function for DllCall().  Updates aDynaParam's type and other attributes.
  11537. // Caller has ensured that aBuf contains exactly two strings (though the second can be NULL).
  11538. {
  11539.     char buf[32], *type_string;
  11540.     int i;
  11541.  
  11542.     // Up to two iterations are done to cover the following cases:
  11543.     // No second type because there was no SYM_VAR to get it from:
  11544.     //    blank means int
  11545.     //    invalid is err
  11546.     // (for the below, note that 2nd can't be blank because var name can't be blank, and the first case above would have caught it if 2nd is NULL)
  11547.     // 1Blank, 2Invalid: blank (but ensure is_unsigned and passed_by_address get reset)
  11548.     // 1Blank, 2Valid: 2
  11549.     // 1Valid, 2Invalid: 1 (second iteration would never have run, so no danger of it having erroneously reset is_unsigned/passed_by_address)
  11550.     // 1Valid, 2Valid: 1 (same comment)
  11551.     // 1Invalid, 2Invalid: invalid
  11552.     // 1Invalid, 2Valid: 2
  11553.  
  11554.     for (i = 0, type_string = aBuf[0]; i < 2 && type_string; type_string = aBuf[++i])
  11555.     {
  11556.         if (toupper(*type_string) == 'U') // Unsigned
  11557.         {
  11558.             aDynaParam.is_unsigned = true;
  11559.             ++type_string; // Omit the 'U' prefix from further consideration.
  11560.         }
  11561.         else
  11562.             aDynaParam.is_unsigned = false;
  11563.  
  11564.         strlcpy(buf, type_string, sizeof(buf)); // Make a modifiable copy for easier parsing below.
  11565.  
  11566.         // v1.0.30.02: The addition of 'P' allows the quotes to be omitted around a pointer type.
  11567.         // However, the current detection below relies upon the fact that not of the types currently
  11568.         // contain the letter P anywhere in them, so it would have to be altered if that ever changes.
  11569.         char *cp = StrChrAny(buf, "*pP"); // Asterisk or the letter P.
  11570.         if (cp)
  11571.         {
  11572.             aDynaParam.passed_by_address = true;
  11573.             // Remove trailing options so that stricmp() can be used below.
  11574.             // Allow optional space in front of asterisk (seems okay even for 'P').
  11575.             if (cp > buf && IS_SPACE_OR_TAB(cp[-1]))
  11576.             {
  11577.                 cp = omit_trailing_whitespace(buf, cp - 1);
  11578.                 cp[1] = '\0'; // Terminate at the leftmost whitespace to remove all whitespace and the suffix.
  11579.             }
  11580.             else
  11581.                 *cp = '\0'; // Terminate at the suffix to remove it.
  11582.         }
  11583.         else
  11584.             aDynaParam.passed_by_address = false;
  11585.  
  11586.         if (!*buf)
  11587.         {
  11588.             // The following also serves to set the default in case this is the first iteration.
  11589.             // Set default but perform second iteration in case the second type string isn't NULL.
  11590.             // In other words, if the second type string is explicitly valid rather than blank,
  11591.             // it should override the following default:
  11592.             aDynaParam.type = DLL_ARG_INT;  // Assume int.  This is relied upon at least for having a return type such as a naked "CDecl".
  11593.             continue; // OK to do this regardless of whether this is the first or second iteration.
  11594.         }
  11595.         else if (!stricmp(buf, "Str"))     aDynaParam.type = DLL_ARG_STR; // The few most common types are kept up top for performance.
  11596.         else if (!stricmp(buf, "Int"))     aDynaParam.type = DLL_ARG_INT;
  11597.         else if (!stricmp(buf, "Short"))   aDynaParam.type = DLL_ARG_SHORT;
  11598.         else if (!stricmp(buf, "Char"))    aDynaParam.type = DLL_ARG_CHAR;
  11599.         else if (!stricmp(buf, "Int64"))   aDynaParam.type = DLL_ARG_INT64;
  11600.         else if (!stricmp(buf, "Float"))   aDynaParam.type = DLL_ARG_FLOAT;
  11601.         else if (!stricmp(buf, "Double"))  aDynaParam.type = DLL_ARG_DOUBLE;
  11602.         // Unnecessary: else if (!stricmp(buf, "None"))    aDynaParam.type = DLL_ARG_NONE;
  11603.         // Otherwise, it's blank or an unknown type, leave it set at the default.
  11604.         else
  11605.         {
  11606.             if (i > 0) // Second iteration.
  11607.             {
  11608.                 // Reset flags to go with any blank value we're falling back to from the first iteration
  11609.                 // (in case our iteration changed the flags based on bogus contents of the second type_string):
  11610.                 aDynaParam.passed_by_address = false;
  11611.                 aDynaParam.is_unsigned = false;
  11612.             }
  11613.             else // First iteration, so aDynaParam.type's value will be set by the second.
  11614.                 continue;
  11615.         }
  11616.         // Since above didn't "continue", the type is explicitly valid so "return" to ensure that
  11617.         // the second iteration doesn't run (in case this is the first iteration):
  11618.         return;
  11619.     }
  11620. }
  11621.  
  11622.  
  11623.  
  11624. void BIF_DllCall(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  11625. // Stores a number or a SYM_STRING result in aResultToken.
  11626. // Sets ErrorLevel to the error code appropriate to any problem that occurred.
  11627. // Caller has set up aParam to be viewable as a left-to-right array of params rather than a stack.
  11628. // It has also ensured that the array has exactly aParamCount items in it.
  11629. // Author: Marcus Sonntag (Ultra)
  11630. {
  11631.     // Set default result in case of early return; a blank value:
  11632.     aResultToken.symbol = SYM_STRING;
  11633.     aResultToken.marker = "";
  11634.     HMODULE hmodule_to_free = NULL; // Set default in case of early goto; mostly for maintainability.
  11635.  
  11636.     // Check that the mandatory first parameter (DLL+Function) is valid.
  11637.     // (load-time validation has ensured at least one parameter is present).
  11638.     void *function; // Will hold the address of the function to be called.
  11639.  
  11640.     switch(aParam[0]->symbol)
  11641.     {
  11642.         case SYM_STRING: // By far the most common, so it's listed first for performance. Also for performance, don't even consider the possibility that a quoted literal string like "33" is a function-address.
  11643.             function = NULL; // Indicate that no function has been specified yet.
  11644.             break;
  11645.         // v1.0.46.08: Allow script to specify the address of a function, which might be useful for
  11646.         // calling functions that the script discovers through unusual means such as C++ member functions.
  11647.         case SYM_INTEGER:
  11648.             // The following is commented out due to  rarity because it can only occur via expressions that produce
  11649.             // a SYM_INTEGER (numeric literals aren't SYM_INTEGER).  If the address is negative, the same result
  11650.             // will occur as for any other invalid address: an ErrorLevel of 0xc0000005.
  11651.             //if (aParam[0]->value_int64 <= 0) // Must be checked before assigning it to "function" so that negatives are still visible (since "function" is unsigned).
  11652.             //{
  11653.             //    g_ErrorLevel->Assign("-1"); // Stage 1 error: Invalid first param.
  11654.             //    return;
  11655.             //}
  11656.             // Otherwise, assume it's a valid address:
  11657.             function = (void *)aParam[0]->value_int64;
  11658.             break;
  11659.         case SYM_FLOAT:
  11660.             g_ErrorLevel->Assign("-1"); // Stage 1 error: Invalid first param.
  11661.             return;
  11662.         default: // SYM_VAR or SYM_OPERAND (SYM_OPERAND is typically a numeric literal, which it seems best to support since it doesn't add any code size or adversely affect performance).
  11663.             char *param1 = (aParam[0]->symbol == SYM_VAR) ? aParam[0]->var->Contents() : aParam[0]->marker;
  11664.             function = (IsPureNumeric(param1, false, false, false))
  11665.                 ? (void *)ATOI64(param1)
  11666.                 : NULL; // Not a pure number, so fall back to normal method of considering it to be path+name.
  11667.             // Due to rarity, the following is commented out (same as the other check above) because it
  11668.             // doesn't seem worth the code size to check it:
  11669.             //if (IsPureNumeric(param1, false, false, false))
  11670.             //{
  11671.             //    __int64 temp64 = ATOI64(param1);
  11672.             //    if (temp64 <= 0)
  11673.             //    {
  11674.             //        g_ErrorLevel->Assign("-1"); // Stage 1 error: Invalid first param.
  11675.             //        return;
  11676.             //    }
  11677.             //    // Otherwise, assume it's a valid address:
  11678.             //    function = (void *)temp64;
  11679.             //}
  11680.             //else // Not a pure number, so fall back to normal method of considering it to be path+name.
  11681.             //    function = NULL; // Indicate that no function has been specified yet.
  11682.     }
  11683.  
  11684.     // Determine the type of return value.
  11685.     DYNAPARM return_attrib = {0}; // Will hold the type and other attributes of the function's return value.
  11686.     int dll_call_mode = DC_CALL_STD; // Set default.  Can be overridden to DC_CALL_CDECL and flags can be OR'd into it.
  11687.     if (aParamCount % 2) // Odd number of parameters indicates the return type has been omitted, so assume BOOL/INT.
  11688.         return_attrib.type = DLL_ARG_INT;
  11689.     else
  11690.     {
  11691.         // Check validity of this arg's return type:
  11692.         ExprTokenType &token = *aParam[aParamCount - 1];
  11693.         if (IS_NUMERIC(token.symbol)) // The return type should be a string, not something purely numeric.
  11694.         {
  11695.             g_ErrorLevel->Assign("-2"); // Stage 2 error: Invalid return type or arg type.
  11696.             return;
  11697.         }
  11698.         char *return_type_string[2];
  11699.         if (token.symbol == SYM_VAR) // SYM_VAR's Type() is always VAR_NORMAL.
  11700.         {
  11701.             return_type_string[0] = token.var->Contents();
  11702.             return_type_string[1] = token.var->mName; // v1.0.33.01: Improve convenience by falling back to the variable's name if the contents are not appropriate.
  11703.         }
  11704.         else
  11705.         {
  11706.             return_type_string[0] = token.marker;
  11707.             return_type_string[1] = NULL;
  11708.         }
  11709.         if (!strnicmp(return_type_string[0], "CDecl", 5)) // Alternate calling convention.
  11710.         {
  11711.             dll_call_mode = DC_CALL_CDECL;
  11712.             return_type_string[0] = omit_leading_whitespace(return_type_string[0] + 5);
  11713.         }
  11714.         // This next part is a little iffy because if a legitimate return type is contained in a variable
  11715.         // that happens to be named Cdecl, Cdecl will be put into effect regardless of what's in the variable.
  11716.         // But the convenience of being able to omit the quotes around Cdecl seems to outweigh the extreme
  11717.         // rarity of such a thing happening.
  11718.         else if (return_type_string[1] && !strnicmp(return_type_string[1], "CDecl", 5)) // Alternate calling convention.
  11719.         {
  11720.             dll_call_mode = DC_CALL_CDECL;
  11721.             return_type_string[1] = NULL; // Must be NULL since return_type_string[1] is the variable's name, by definition, so it can't have any spaces in it, and thus no space delimited items after "Cdecl".
  11722.         }
  11723.         ConvertDllArgType(return_type_string, return_attrib);
  11724.         if (return_attrib.type == DLL_ARG_INVALID)
  11725.         {
  11726.             g_ErrorLevel->Assign("-2"); // Stage 2 error: Invalid return type or arg type.
  11727.             return;
  11728.         }
  11729.         --aParamCount;  // Remove the last parameter from further consideration.
  11730.         if (!return_attrib.passed_by_address) // i.e. the special return flags below are not needed when an address is being returned.
  11731.         {
  11732.             if (return_attrib.type == DLL_ARG_DOUBLE)
  11733.                 dll_call_mode |= DC_RETVAL_MATH8;
  11734.             else if (return_attrib.type == DLL_ARG_FLOAT)
  11735.                 dll_call_mode |= DC_RETVAL_MATH4;
  11736.         }
  11737.     }
  11738.  
  11739.     // Using stack memory, create an array of dll args large enough to hold the actual number of args present.
  11740.     int arg_count = aParamCount/2; // Might provide one extra due to first/last params, which is inconsequential.
  11741.     DYNAPARM *dyna_param = arg_count ? (DYNAPARM *)_alloca(arg_count * sizeof(DYNAPARM)) : NULL;
  11742.     // Above: _alloca() has been checked for code-bloat and it doesn't appear to be an issue.
  11743.     // Above: Fix for v1.0.36.07: According to MSDN, on failure, this implementation of _alloca() generates a
  11744.     // stack overflow exception rather than returning a NULL value.  Therefore, NULL is no longer checked,
  11745.     // nor is an exception block used since stack overflow in this case should be exceptionally rare (if it
  11746.     // does happen, it would probably mean the script or the program has a design flaw somewhere, such as
  11747.     // infinite recursion).
  11748.  
  11749.     char *arg_type_string[2], *arg_as_string;
  11750.     int i;
  11751.  
  11752.     // Above has already ensured that after the first parameter, there are either zero additional parameters
  11753.     // or an even number of them.  In other words, each arg type will have an arg value to go with it.
  11754.     // It has also verified that the dyna_param array is large enough to hold all of the args.
  11755.     for (arg_count = 0, i = 1; i < aParamCount; ++arg_count, i += 2)  // Same loop as used later below, so maintain them together.
  11756.     {
  11757.         // Check validity of this arg's type and contents:
  11758.         if (IS_NUMERIC(aParam[i]->symbol)) // The arg type should be a string, not something purely numeric.
  11759.         {
  11760.             g_ErrorLevel->Assign("-2"); // Stage 2 error: Invalid return type or arg type.
  11761.             return;
  11762.         }
  11763.         // Otherwise, this arg's type is a string as it should be, so retrieve it:
  11764.         if (aParam[i]->symbol == SYM_VAR) // SYM_VAR's Type() is always VAR_NORMAL.
  11765.         {
  11766.             arg_type_string[0] = aParam[i]->var->Contents();
  11767.             arg_type_string[1] = aParam[i]->var->mName;
  11768.             // v1.0.33.01: arg_type_string[1] improves convenience by falling back to the variable's name
  11769.             // if the contents are not appropriate.  In other words, both Int and "Int" are treated the same.
  11770.             // It's done this way to allow the variable named "Int" to actually contain some other legitimate
  11771.             // type-name such as "Str" (in case anyone ever happens to do that).
  11772.         }
  11773.         else
  11774.         {
  11775.             arg_type_string[0] = aParam[i]->marker;
  11776.             arg_type_string[1] = NULL;
  11777.         }
  11778.  
  11779.         ExprTokenType &this_param = *aParam[i + 1];         // Resolved for performance and convenience.
  11780.         DYNAPARM &this_dyna_param = dyna_param[arg_count];  //
  11781.  
  11782.         // If the arg's contents is a string, resolve it once here to simplify things that reference it later.
  11783.         // NOTE: aResultToken.buf is not used here to resolve a number to a string because although it would
  11784.         // add a little flexibility by allowing up to one string parameter to be a numeric expression, it
  11785.         // seems to add more complexity and confusion than its worth to other sections further below:
  11786.         if (IS_NUMERIC(this_param.symbol))
  11787.             arg_as_string = NULL;
  11788.         else
  11789.         {
  11790.             if (this_param.symbol == SYM_VAR) // SYM_VAR's Type() is always VAR_NORMAL.
  11791.             {
  11792.                 arg_as_string = this_param.var->Contents(); // See below.
  11793.                 // UPDATE: The v1.0.44.14 item below doesn't work in release mode, only debug mode (turning off
  11794.                 // "string pooling" doesn't help either).  So it's commented out until a way is found
  11795.                 // to pass the address of a read-only empty string (if such a thing is possible in
  11796.                 // release mode).  Such a string should have the following properties:
  11797.                 // 1) The first byte at its address should be '\0' so that functions can read it
  11798.                 //    and recognize it as a valid empty string.
  11799.                 // 2) The memory address should be readable but not writable: it should throw an
  11800.                 //    access violation if the function tries to write to it (like "" does in debug mode).
  11801.                 // SO INSTEAD of the following, DllCall() now checks further below for whether sEmptyString
  11802.                 // has been overwritten/trashed by the call, and if so displays a warning dialog.
  11803.                 // See note above about this: v1.0.44.14: If a variable is being passed that has no capacity, pass a
  11804.                 // read-only memory area instead of a writable empty string. There are two big benefits to this:
  11805.                 // 1) It forces an immediate exception (catchable by DllCall's exception handler) so
  11806.                 //    that the program doesn't crash from memory corruption later on.
  11807.                 // 2) It avoids corrupting the program's static memory area (because sEmptyString
  11808.                 //    resides there), which can save many hours of debugging for users when the program
  11809.                 //    crashes on some seemingly unrelated line.
  11810.                 // Of course, it's not a complete solution because it doesn't stop a script from
  11811.                 // passing a variable whose capacity is non-zero yet too small to handle what the
  11812.                 // function will write to it.  But it's a far cry better than nothing because it's
  11813.                 // common for a script to forget to call VarSetCapacity before psssing a buffer to some
  11814.                 // function that writes a string to it.
  11815.                 //if (arg_as_string == Var::sEmptyString) // To improve performance, compare directly to Var::sEmptyString rather than calling Capacity().
  11816.                 //    arg_as_string = ""; // Make it read-only to force an exception.  See comments above.
  11817.             }
  11818.             else
  11819.                 arg_as_string = this_param.marker;
  11820.         }
  11821.  
  11822.         // Store the each arg into a dyna_param struct, using its arg type to determine how.
  11823.         ConvertDllArgType(arg_type_string, this_dyna_param);
  11824.         switch (this_dyna_param.type)
  11825.         {
  11826.         case DLL_ARG_STR:
  11827.             if (arg_as_string)
  11828.                 this_dyna_param.str = arg_as_string;
  11829.             else
  11830.             {
  11831.                 // For now, string args must be real strings rather than floats or ints.  An alternative
  11832.                 // to this would be to convert it to number using persistent memory from the caller (which
  11833.                 // is necessary because our own stack memory should not be passed to any function since
  11834.                 // that might cause it to return a pointer to stack memory, or update an output-parameter
  11835.                 // to be stack memory, which would be invalid memory upon return to the caller).
  11836.                 // The complexity of this doesn't seem worth the rarity of the need, so this will be
  11837.                 // documented in the help file.
  11838.                 g_ErrorLevel->Assign("-2"); // Stage 2 error: Invalid return type or arg type.
  11839.                 return;
  11840.             }
  11841.             break;
  11842.  
  11843.         case DLL_ARG_DOUBLE:
  11844.         case DLL_ARG_FLOAT:
  11845.             // This currently doesn't validate that this_dyna_param.is_unsigned==false, since it seems
  11846.             // too rare and mostly harmless to worry about something like "Ufloat" having been specified.
  11847.             if (arg_as_string)
  11848.                 this_dyna_param.value_double = (double)ATOF(arg_as_string);
  11849.             else if (this_param.symbol == SYM_INTEGER)
  11850.                 this_dyna_param.value_double = (double)this_param.value_int64;
  11851.             else
  11852.                 this_dyna_param.value_double = this_param.value_double;
  11853.  
  11854.             if (this_dyna_param.type == DLL_ARG_FLOAT)
  11855.                 this_dyna_param.value_float = (float)this_dyna_param.value_double;
  11856.             break;
  11857.  
  11858.         case DLL_ARG_INVALID:
  11859.             g_ErrorLevel->Assign("-2"); // Stage 2 error: Invalid return type or arg type.
  11860.             return;
  11861.  
  11862.         default: // Namely:
  11863.         //case DLL_ARG_INT:
  11864.         //case DLL_ARG_SHORT:
  11865.         //case DLL_ARG_CHAR:
  11866.         //case DLL_ARG_INT64:
  11867.             if (arg_as_string)
  11868.             {
  11869.                 // Support for unsigned values that are 32 bits wide or less is done via ATOI64() since
  11870.                 // it should be able to handle both signed and unsigned values.  However, unsigned 64-bit
  11871.                 // values probably require ATOU64(), which will prevent something like -1 from being seen
  11872.                 // as the largest unsigned 64-bit int; but more importantly there are some other issues
  11873.                 // with unsigned 64-bit numbers: The script internals use 64-bit signed values everywhere,
  11874.                 // so unsigned values can only be partially supported for incoming parameters, but probably
  11875.                 // not for outgoing parameters (values the function changed) or the return value.  Those
  11876.                 // should probably be written back out to the script as negatives so that other parts of
  11877.                 // the script, such as expressions, can see them as signed values.  In other words, if the
  11878.                 // script somehow gets a 64-bit unsigned value into a variable, and that value is larger
  11879.                 // that LLONG_MAX (i.e. too large for ATOI64 to handle), ATOU64() will be able to resolve
  11880.                 // it, but any output parameter should be written back out as a negative if it exceeds
  11881.                 // LLONG_MAX (return values can be written out as unsigned since the script can specify
  11882.                 // signed to avoid this, since they don't need the incoming detection for ATOU()).
  11883.                 if (this_dyna_param.is_unsigned && this_dyna_param.type == DLL_ARG_INT64)
  11884.                     this_dyna_param.value_int64 = (__int64)ATOU64(arg_as_string); // Cast should not prevent called function from seeing it as an undamaged unsigned number.
  11885.                 else
  11886.                     this_dyna_param.value_int64 = ATOI64(arg_as_string);
  11887.             }
  11888.             else if (this_param.symbol == SYM_INTEGER)
  11889.                 this_dyna_param.value_int64 = this_param.value_int64;
  11890.             else
  11891.                 this_dyna_param.value_int64 = (__int64)this_param.value_double;
  11892.  
  11893.             // Values less than or equal to 32-bits wide always get copied into a single 32-bit value
  11894.             // because they should be right justified within it for insertion onto the call stack.
  11895.             if (this_dyna_param.type != DLL_ARG_INT64) // Shift the 32-bit value into the high-order DWORD of the 64-bit value for later use by DynaCall().
  11896.                 this_dyna_param.value_int = (int)this_dyna_param.value_int64; // Force a failure if compiler generates code for this that corrupts the union (since the same method is used for the more obscure float vs. double below).
  11897.         } // switch (this_dyna_param.type)
  11898.     } // for() each arg.
  11899.     
  11900.     if (!function) // The function's address hasn't yet been determined.
  11901.     {
  11902.         char param1_buf[MAX_PATH*2], *function_name, *dll_name; // Must use MAX_PATH*2 because the function name is INSIDE the Dll file, and thus MAX_PATH can be exceeded.
  11903.         // Define the standard libraries here. If they reside in %SYSTEMROOT%\system32 it is not
  11904.         // necessary to specify the full path (it wouldn't make sense anyway).
  11905.         static HMODULE sStdModule[] = {GetModuleHandle("user32"), GetModuleHandle("kernel32")
  11906.             , GetModuleHandle("comctl32"), GetModuleHandle("gdi32")}; // user32 is listed first for performance.
  11907.         static int sStdModule_count = sizeof(sStdModule) / sizeof(HMODULE);
  11908.  
  11909.         // Make a modifiable copy of param1 so that the DLL name and function name can be parsed out easily:
  11910.         strlcpy(param1_buf, aParam[0]->symbol == SYM_VAR ? aParam[0]->var->Contents() : aParam[0]->marker, sizeof(param1_buf) - 1); // -1 to reserve space for the "A" suffix later below.
  11911.         if (   !(function_name = strrchr(param1_buf, '\\'))   ) // No DLL name specified, so a search among standard defaults will be done.
  11912.         {
  11913.             dll_name = NULL;
  11914.             function_name = param1_buf;
  11915.  
  11916.             // Since no DLL was specified, search for the specified function among the standard modules.
  11917.             for (i = 0; i < sStdModule_count; ++i)
  11918.                 if (   sStdModule[i] && (function = (void *)GetProcAddress(sStdModule[i], function_name))   )
  11919.                     break;
  11920.             if (!function)
  11921.             {
  11922.                 // Since the absence of the "A" suffix (e.g. MessageBoxA) is so common, try it that way
  11923.                 // but only here with the standard libraries since the risk of ambiguity (calling the wrong
  11924.                 // function) seems unacceptably high in a custom DLL.  For example, a custom DLL might have
  11925.                 // function called "AA" but not one called "A".
  11926.                 strcat(function_name, "A"); // 1 byte of memory was already reserved above for the 'A'.
  11927.                 for (i = 0; i < sStdModule_count; ++i)
  11928.                     if (   sStdModule[i] && (function = (void *)GetProcAddress(sStdModule[i], function_name))   )
  11929.                         break;
  11930.             }
  11931.         }
  11932.         else // DLL file name is explicitly present.
  11933.         {
  11934.             dll_name = param1_buf;
  11935.             *function_name = '\0';  // Terminate dll_name to split it off from function_name.
  11936.             ++function_name; // Set it to the character after the last backslash.
  11937.  
  11938.             // Get module handle. This will work when DLL is already loaded and might improve performance if
  11939.             // LoadLibrary is a high-overhead call even when the library already being loaded.  If
  11940.             // GetModuleHandle() fails, fall back to LoadLibrary().
  11941.             HMODULE hmodule;
  11942.             if (   !(hmodule = GetModuleHandle(dll_name))    )
  11943.                 if (   !(hmodule = hmodule_to_free = LoadLibrary(dll_name))   )
  11944.                 {
  11945.                     g_ErrorLevel->Assign("-3"); // Stage 3 error: DLL couldn't be loaded.
  11946.                     return;
  11947.                 }
  11948.             if (   !(function = (void *)GetProcAddress(hmodule, function_name))   )
  11949.             {
  11950.                 // v1.0.34: If it's one of the standard libraries, try the "A" suffix.
  11951.                 for (i = 0; i < sStdModule_count; ++i)
  11952.                     if (hmodule == sStdModule[i]) // Match found.
  11953.                     {
  11954.                         strcat(function_name, "A"); // 1 byte of memory was already reserved above for the 'A'.
  11955.                         function = (void *)GetProcAddress(hmodule, function_name);
  11956.                         break;
  11957.                     }
  11958.             }
  11959.         }
  11960.  
  11961.         if (!function)
  11962.         {
  11963.             g_ErrorLevel->Assign("-4"); // Stage 4 error: Function could not be found in the DLL(s).
  11964.             goto end;
  11965.         }
  11966.     }
  11967.  
  11968.     ////////////////////////
  11969.     // Call the DLL function
  11970.     ////////////////////////
  11971.     DWORD exception_occurred; // Must not be named "exception_code" to avoid interfering with MSVC macros.
  11972.     DYNARESULT return_value;  // Doing assignment as separate step avoids compiler warning about "goto end" skipping it.
  11973.     return_value = DynaCall(dll_call_mode, function, dyna_param, arg_count, exception_occurred, NULL, 0);
  11974.     // The above has also set g_ErrorLevel appropriately.
  11975.  
  11976.     if (*Var::sEmptyString)
  11977.     {
  11978.         // v1.0.45.01 Above has detected that a variable of zero capacity was passed to the called function
  11979.         // and the function wrote to it (assuming sEmptyString wasn't already trashed some other way even
  11980.         // before the call).  So patch up the empty string to stabilize a little; but it's too late to
  11981.         // salvage this instance of the program because there's no knowing how much static data adjacent to
  11982.         // sEmptyString has been overwritten and corrupted.
  11983.         *Var::sEmptyString = '\0';
  11984.         // Don't bother with freeing hmodule_to_free since a critical error like this calls for minimal cleanup.
  11985.         // The OS almost certainly frees it upon termination anyway.
  11986.         // Call ScriptErrror() so that the user knows *which* DllCall is at fault:
  11987.         g_script.ScriptError("This DllCall requires a prior VarSetCapacity. The program is now unstable and will exit.");
  11988.         g_script.ExitApp(EXIT_CRITICAL); // Called this way, it will run the OnExit routine, which is debatable because it could cause more good than harm, but might avoid loss of data if the OnExit routine does something important.
  11989.     }
  11990.  
  11991.     // It seems best to have the above take precedence over "exception_occurred" below.
  11992.     if (exception_occurred)
  11993.     {
  11994.         // If the called function generated an exception, I think it's impossible for the return value
  11995.         // to be valid/meaningful since it the function never returned properly.  Confirmation of this
  11996.         // would be good, but in the meantime it seems best to make the return value an empty string as
  11997.         // an indicator that the call failed (in addition to ErrorLevel).
  11998.         aResultToken.symbol = SYM_STRING;
  11999.         aResultToken.marker = "";
  12000.         // But continue on to write out any output parameters because the called function might have
  12001.         // had a chance to update them before aborting.
  12002.     }
  12003.     else // The call was successful.  Interpret and store the return value.
  12004.     {
  12005.         // If the return value is passed by address, dereference it here.
  12006.         if (return_attrib.passed_by_address)
  12007.         {
  12008.             return_attrib.passed_by_address = false; // Because the address is about to be dereferenced/resolved.
  12009.  
  12010.             switch(return_attrib.type)
  12011.             {
  12012.             case DLL_ARG_INT64:
  12013.             case DLL_ARG_DOUBLE:
  12014.                 // Same as next section but for eight bytes:
  12015.                 return_value.Int64 = *(__int64 *)return_value.Pointer;
  12016.                 break;
  12017.             default: // Namely:
  12018.             //case DLL_ARG_STR:  // Even strings can be passed by address, which is equivalent to "char **".
  12019.             //case DLL_ARG_INT:
  12020.             //case DLL_ARG_SHORT:
  12021.             //case DLL_ARG_CHAR:
  12022.             //case DLL_ARG_FLOAT:
  12023.                 // All the above are stored in four bytes, so a straight dereference will copy the value
  12024.                 // over unchanged, even if it's a float.
  12025.                 return_value.Int = *(int *)return_value.Pointer;
  12026.             }
  12027.         }
  12028.  
  12029.         switch(return_attrib.type)
  12030.         {
  12031.         case DLL_ARG_STR:
  12032.             // The contents of the string returned from the function must not reside in our stack memory since
  12033.             // that will vanish when we return to our caller.  As long as every string that went into the
  12034.             // function isn't on our stack (which is the case), there should be no way for what comes out to be
  12035.             // on the stack either.
  12036.             aResultToken.symbol = SYM_STRING;
  12037.             aResultToken.marker = (char *)(return_value.Pointer ? return_value.Pointer : "");
  12038.             // Above: Fix for v1.0.33.01: Don't allow marker to be set to NULL, which prevents crash
  12039.             // with something like the following, which in this case probably happens because the inner
  12040.             // call produces a non-numeric string, which "int" then sees as zero, which CharLower() then
  12041.             // sees as NULL, which causes CharLower to return NULL rather than a real string:
  12042.             //result := DllCall("CharLower", "int", DllCall("CharUpper", "str", MyVar, "str"), "str")
  12043.             break;
  12044.         case DLL_ARG_INT: // If the function has a void return value (formerly DLL_ARG_NONE), the value assigned here is undefined and inconsequential since the script should be designed to ignore it.
  12045.             aResultToken.symbol = SYM_INTEGER;
  12046.             if (return_attrib.is_unsigned)
  12047.                 aResultToken.value_int64 = (UINT)return_value.Int; // Preserve unsigned nature upon promotion to signed 64-bit.
  12048.             else // Signed.
  12049.                 aResultToken.value_int64 = return_value.Int;
  12050.             break;
  12051.         case DLL_ARG_SHORT:
  12052.             aResultToken.symbol = SYM_INTEGER;
  12053.             if (return_attrib.is_unsigned)
  12054.                 aResultToken.value_int64 = return_value.Int & 0x0000FFFF; // This also forces the value into the unsigned domain of a signed int.
  12055.             else // Signed.
  12056.                 aResultToken.value_int64 = (SHORT)(WORD)return_value.Int; // These casts properly preserve negatives.
  12057.             break;
  12058.         case DLL_ARG_CHAR:
  12059.             aResultToken.symbol = SYM_INTEGER;
  12060.             if (return_attrib.is_unsigned)
  12061.                 aResultToken.value_int64 = return_value.Int & 0x000000FF; // This also forces the value into the unsigned domain of a signed int.
  12062.             else // Signed.
  12063.                 aResultToken.value_int64 = (char)(BYTE)return_value.Int; // These casts properly preserve negatives.
  12064.             break;
  12065.         case DLL_ARG_INT64:
  12066.             // Even for unsigned 64-bit values, it seems best both for simplicity and consistency to write
  12067.             // them back out to the script as signed values because script internals are not currently
  12068.             // equipped to handle unsigned 64-bit values.  This has been documented.
  12069.             aResultToken.symbol = SYM_INTEGER;
  12070.             aResultToken.value_int64 = return_value.Int64;
  12071.             break;
  12072.         case DLL_ARG_FLOAT:
  12073.             aResultToken.symbol = SYM_FLOAT;
  12074.             aResultToken.value_double = return_value.Float;
  12075.             break;
  12076.         case DLL_ARG_DOUBLE:
  12077.             aResultToken.symbol = SYM_FLOAT; // There is no SYM_DOUBLE since all floats are stored as doubles.
  12078.             aResultToken.value_double = return_value.Double;
  12079.             break;
  12080.         //default: // Should never be reached unless there's a bug.
  12081.         //    aResultToken.symbol = SYM_STRING;
  12082.         //    aResultToken.marker = "";
  12083.         } // switch(return_attrib.type)
  12084.     } // Storing the return value when no exception occurred.
  12085.  
  12086.     // Store any output parameters back into the input variables.  This allows a function to change the
  12087.     // contents of a variable for the following arg types: String and Pointer to <various number types>.
  12088.     for (arg_count = 0, i = 1; i < aParamCount; ++arg_count, i += 2) // Same loop as used above, so maintain them together.
  12089.     {
  12090.         ExprTokenType &this_param = *aParam[i + 1];  // This and the next are resolved for performance and convenience.
  12091.         DYNAPARM &this_dyna_param = dyna_param[arg_count];
  12092.  
  12093.         if (this_param.symbol != SYM_VAR) // Output parameters are copied back only if its counterpart parameter is a naked variable.
  12094.             continue;
  12095.         Var &output_var = *this_param.var; // For performance and convenience.
  12096.         if (this_dyna_param.type == DLL_ARG_STR) // The function might have altered Contents(), so update Length().
  12097.         {
  12098.             char *contents = output_var.Contents();
  12099.             VarSizeType capacity = output_var.Capacity();
  12100.             // Since the performance cost is low, ensure the string is terminated at the limit of its
  12101.             // capacity (helps prevent crashes if DLL function didn't do its job and terminate the string,
  12102.             // or when a function is called that deliberately doesn't terminate the string, such as
  12103.             // RtlMoveMemory()).
  12104.             if (capacity)
  12105.                 contents[capacity - 1] = '\0';
  12106.             output_var.Length() = (VarSizeType)strlen(contents);
  12107.             continue;
  12108.         }
  12109.  
  12110.         // Since above didn't "continue", this arg wasn't passed as a string.  Of the remaining types, only
  12111.         // those passed by address can possibly be output parameters, so skip the rest:
  12112.         if (!this_dyna_param.passed_by_address)
  12113.             continue;
  12114.  
  12115.         switch (this_dyna_param.type)
  12116.         {
  12117.         // case DLL_ARG_STR:  Already handled above.
  12118.         case DLL_ARG_INT:
  12119.             if (this_dyna_param.is_unsigned)
  12120.                 output_var.Assign((DWORD)this_dyna_param.value_int);
  12121.             else // Signed.
  12122.                 output_var.Assign(this_dyna_param.value_int);
  12123.             break;
  12124.         case DLL_ARG_SHORT:
  12125.             if (this_dyna_param.is_unsigned) // Force omission of the high-order word in case it is non-zero from a parameter that was originally and erroneously larger than a short.
  12126.                 output_var.Assign(this_dyna_param.value_int & 0x0000FFFF); // This also forces the value into the unsigned domain of a signed int.
  12127.             else // Signed.
  12128.                 output_var.Assign((int)(SHORT)(WORD)this_dyna_param.value_int); // These casts properly preserve negatives.
  12129.             break;
  12130.         case DLL_ARG_CHAR:
  12131.             if (this_dyna_param.is_unsigned) // Force omission of the high-order word in case it is non-zero from a parameter that was originally and erroneously larger than a short.
  12132.                 output_var.Assign(this_dyna_param.value_int & 0x000000FF); // This also forces the value into the unsigned domain of a signed int.
  12133.             else // Signed.
  12134.                 output_var.Assign((int)(char)(BYTE)this_dyna_param.value_int); // These casts properly preserve negatives.
  12135.             break;
  12136.         case DLL_ARG_INT64: // Unsigned and signed are both written as signed for the reasons described elsewhere above.
  12137.             output_var.Assign(this_dyna_param.value_int64);
  12138.             break;
  12139.         case DLL_ARG_FLOAT:
  12140.             output_var.Assign(this_dyna_param.value_float);
  12141.             break;
  12142.         case DLL_ARG_DOUBLE:
  12143.             output_var.Assign(this_dyna_param.value_double);
  12144.             break;
  12145.         }
  12146.     }
  12147.  
  12148. end:
  12149.     if (hmodule_to_free)
  12150.         FreeLibrary(hmodule_to_free);
  12151. }
  12152.  
  12153.  
  12154.  
  12155. void BIF_StrLen(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  12156. // Caller has ensured that SYM_VAR's Type() is VAR_NORMAL and that it's either not an environment
  12157. // variable or the caller wants environment varibles treated as having zero length.
  12158. // Result is always an integer (caller has set aResultToken.symbol to a default of SYM_INTEGER, so no need
  12159. // to set it here).
  12160. {
  12161.     // Loadtime validation has ensured that there's exactly one actual parameter.
  12162.     // Calling Length() is always valid for SYM_VAR because SYM_VAR's Type() is always VAR_NORMAL.
  12163.     aResultToken.value_int64 = (aParam[0]->symbol == SYM_VAR)
  12164.         ? aParam[0]->var->Length() + aParam[0]->var->IsBinaryClip() // i.e. Add 1 if it's binary-clipboard, as documented.
  12165.         : strlen(ExprTokenToString(*aParam[0], aResultToken.buf));  // Allow StrLen(numeric_expr) for flexibility.
  12166. }
  12167.  
  12168.  
  12169.  
  12170. void BIF_SubStr(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount) // Added in v1.0.46.
  12171. {
  12172.     // Set default return value in case of early return.
  12173.     aResultToken.symbol = SYM_STRING;
  12174.     aResultToken.marker = "";
  12175.  
  12176.     // Get the first arg, which is the string used as the source of the extraction. Call it "haystack" for clarity.
  12177.     char haystack_buf[MAX_FORMATTED_NUMBER_LENGTH + 1]; // A separate buf because aResultToken.buf is sometimes used to store the result.
  12178.     char *haystack = ExprTokenToString(*aParam[0], haystack_buf); // Remember that aResultToken.buf is part of a union, though in this case there's no danger of overwriting it since our result will always be of STRING type (not int or float).
  12179.     int haystack_length = (int)EXPR_TOKEN_LENGTH(aParam[0], haystack);
  12180.  
  12181.     // Load-time validation has ensured that at least the first two parameters are present:
  12182.     int starting_offset = (int)ExprTokenToInt64(*aParam[1]) - 1; // The one-based starting position in haystack (if any).  Convert it to zero-based.
  12183.     if (starting_offset > haystack_length)
  12184.         return; // Yield the empty string (a default set higher above).
  12185.     if (starting_offset < 0) // Same convention as RegExMatch/Replace(): Treat a StartingPos of 0 (offset -1) as "start at the string's last char".  Similarly, treat negatives as starting further to the left of the end of the string.
  12186.     {
  12187.         starting_offset += haystack_length;
  12188.         if (starting_offset < 0)
  12189.             starting_offset = 0;
  12190.     }
  12191.  
  12192.     int remaining_length_available = haystack_length - starting_offset;
  12193.     int extract_length;
  12194.     if (aParamCount < 3) // No length specified, so extract all the remaining length.
  12195.         extract_length = remaining_length_available;
  12196.     else
  12197.     {
  12198.         if (   !(extract_length = (int)ExprTokenToInt64(*aParam[2]))   )  // It has asked to extract zero characters.
  12199.             return; // Yield the empty string (a default set higher above).
  12200.         if (extract_length < 0)
  12201.         {
  12202.             extract_length += remaining_length_available; // Result is the number of characters to be extracted (i.e. after omitting the number of chars specified in extract_length).
  12203.             if (extract_length < 1) // It has asked to omit all characters.
  12204.                 return; // Yield the empty string (a default set higher above).
  12205.         }
  12206.         else // extract_length > 0
  12207.             if (extract_length > remaining_length_available)
  12208.                 extract_length = remaining_length_available;
  12209.     }
  12210.  
  12211.     // Above has set extract_length to the exact number of characters that will actually be extracted.
  12212.     char *result = haystack + starting_offset; // This is the result except for the possible need to truncate it below.
  12213.  
  12214.     if (extract_length == remaining_length_available) // All of haystack is desired (starting at starting_offset).
  12215.     {
  12216.         aResultToken.marker = result; // No need for any copying or termination, just send back part of haystack.
  12217.         return;                       // Caller and Var:Assign() know that overlap is possible, so this seems safe.
  12218.     }
  12219.     
  12220.     // Otherwise, at least one character is being omitted from the end of haystack.  So need a more complex method.
  12221.     if (extract_length <= MAX_FORMATTED_NUMBER_LENGTH) // v1.0.46.01: Avoid malloc() for small strings.  However, this improves speed by only 10% in a test where random 25-byte strings were extracted from a 700 KB string (probably because VC++'s malloc()/free() are very fast for small allocations).
  12222.         aResultToken.marker = aResultToken.buf; // Store the address of the result for the caller.
  12223.     else
  12224.     {
  12225.         // Otherwise, validation higher above has ensured: extract_length < remaining_length_available.
  12226.         // Caller has provided a NULL circuit_token as a means of passing back memory we allocate here.
  12227.         // So if we change "result" to be non-NULL, the caller will take over responsibility for freeing that memory.
  12228.         if (   !(aResultToken.circuit_token = (ExprTokenType *)malloc(extract_length + 1))   ) // Out of memory. Due to rarity, don't display an error dialog (there's currently no way for a built-in function to abort the current thread anyway?)
  12229.             return; // Yield the empty string (a default set higher above).
  12230.         aResultToken.marker = (char *)aResultToken.circuit_token; // Store the address of the result for the caller.
  12231.         aResultToken.buf = (char *)(size_t)extract_length; // MANDATORY FOR USERS OF CIRCUIT_TOKEN: "buf" is being overloaded to store the length for our caller.
  12232.     }
  12233.     memcpy(aResultToken.marker, result, extract_length);
  12234.     aResultToken.marker[extract_length] = '\0'; // Must be done separately from the memcpy() because the memcpy() might just be taking a substring (i.e. long before result's terminator).
  12235. }
  12236.  
  12237.  
  12238.  
  12239. void BIF_InStr(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  12240. {
  12241.     // Load-time validation has already ensured that at least two actual parameters are present.
  12242.     char needle_buf[MAX_FORMATTED_NUMBER_LENGTH + 1];
  12243.     char *haystack = ExprTokenToString(*aParam[0], aResultToken.buf);
  12244.     char *needle = ExprTokenToString(*aParam[1], needle_buf);
  12245.     // Result type will always be an integer:
  12246.     // Caller has set aResultToken.symbol to a default of SYM_INTEGER, so no need to set it here.
  12247.  
  12248.     // v1.0.43.03: Rather than adding a third value to the CaseSensitive parameter, it seems better to
  12249.     // obey StringCaseSense because:
  12250.     // 1) It matches the behavior of the equal operator (=) in expressions.
  12251.     // 2) It's more friendly for typical international uses because it avoids having to specify that special/third value
  12252.     //    for every call of InStr.  It's nice to be able to omit the CaseSensitive parameter every time and know that
  12253.     //    the behavior of both InStr and its counterpart the equals operator are always consistent with each other.
  12254.     // 3) Avoids breaking existing scripts that may pass something other than true/false for the CaseSense parameter.
  12255.     StringCaseSenseType string_case_sense = (StringCaseSenseType)(aParamCount >= 3 && ExprTokenToInt64(*aParam[2]));
  12256.     // Above has assigned SCS_INSENSITIVE (0) or SCS_SENSITIVE (1).  If it's insensitive, resolve it to
  12257.     // be Locale-mode if the StringCaseSense mode is either case-sensitive or Locale-insensitive.
  12258.     if (g.StringCaseSense != SCS_INSENSITIVE && string_case_sense == SCS_INSENSITIVE) // Ordered for short-circuit performance.
  12259.         string_case_sense = SCS_INSENSITIVE_LOCALE;
  12260.  
  12261.     char *found_pos;
  12262.     __int64 offset = 0; // Set default.
  12263.  
  12264.     if (aParamCount >= 4) // There is a starting position present.
  12265.     {
  12266.         offset = ExprTokenToInt64(*aParam[3]) - 1; // i.e. the fourth arg.
  12267.         if (offset == -1) // Special mode to search from the right side.  Other negative values are reserved for possible future use as offsets from the right side.
  12268.         {
  12269.             found_pos = strrstr(haystack, needle, string_case_sense, 1);
  12270.             aResultToken.value_int64 = found_pos ? (found_pos - haystack + 1) : 0;  // +1 to convert to 1-based, since 0 indicates "not found".
  12271.             return;
  12272.         }
  12273.         // Otherwise, offset is less than -1 or >= 0.
  12274.         // Since InStr("", "") yields 1, it seems consistent for InStr("Red", "", 4) to yield
  12275.         // 4 rather than 0.  The below takes this into account:
  12276.         if (offset < 0 || offset > // ...greater-than the length of haystack calculated below.
  12277.             (aParam[0]->symbol == SYM_VAR  // LengthIgnoreBinaryClip() is used because InStr() doesn't recognize/support binary-clip, so treat it as a normal string (i.e. find first binary zero via strlen()).
  12278.                 ? aParam[0]->var->LengthIgnoreBinaryClip() : strlen(haystack)))
  12279.         {
  12280.             aResultToken.value_int64 = 0; // Match never found when offset is beyond length of string.
  12281.             return;
  12282.         }
  12283.     }
  12284.     // Since above didn't return:
  12285.     haystack += offset; // Above has verified that this won't exceed the length of haystack.
  12286.     found_pos = strstr2(haystack, needle, string_case_sense);
  12287.     aResultToken.value_int64 = found_pos ? (found_pos - haystack + offset + 1) : 0;
  12288. }
  12289.  
  12290.  
  12291.  
  12292. pcre *get_compiled_regex(char *aRegEx, bool &aGetPositionsNotSubstrings, pcre_extra *&aExtra
  12293.     , ExprTokenType *aResultToken)
  12294. // Returns the compiled RegEx, or NULL on failure.
  12295. // This function is called by things other than built-in functions so it should be kept general-purpose.
  12296. // Upon failure, if aResultToken!=NULL:
  12297. //   - ErrorLevel is set to a descriptive string other than "0".
  12298. //   - *aResultToken is set up to contain an empty string.
  12299. // Upon success, the following output parameters are set based on the options that were specified:
  12300. //    aGetPositionsNotSubstrings
  12301. //    aExtra
  12302. //    (but it doesn't change ErrorLevel on success, not even if aResultToken!=NULL)
  12303. {
  12304.     // While reading from or writing to the cache, don't allow another thread entry.  This is because
  12305.     // that thread (or this one) might write to the cache while the other one is reading/writing, which
  12306.     // could cause loss of data integrity (the hook thread can enter here via #IfWin & SetTitleMatchMode RegEx).
  12307.     // Together, Enter/LeaveCriticalSection reduce performance by only 1.4% in the tightest possible script
  12308.     // loop that hits the first cache entry every time.  So that's the worst case except when there's an actual
  12309.     // collision, in which case performance suffers more because internally, EnterCriticalSection() does a
  12310.     // wait/semaphore operation, which is more costly.
  12311.     // Finally, the code size of all critical-section features together is less than 512 bytes (uncompressed),
  12312.     // so like performance, that's not a concern either.
  12313.     EnterCriticalSection(&g_CriticalRegExCache); // Request ownership of the critical section. If another thread already owns it, this thread will block until the other thread finishes.
  12314.  
  12315.     // SET UP THE CACHE.
  12316.     // This is a very crude cache for linear search. Of course, hashing would be better in the sense that it
  12317.     // would allow the cache to get much larger while still being fast (I believe PHP caches up to 4096 items).
  12318.     // Binary search might not be such a good idea in this case due to the time required to find the right spot
  12319.     // to insert a new cache item (however, items aren't inserted often, so it might perform quite well until
  12320.     // the cache contained thousands of RegEx's, which is unlikely to ever happen in most scripts).
  12321.     struct pcre_cache_entry
  12322.     {
  12323.         // For simplicity (and thus performance), the entire RegEx pattern including its options is cached
  12324.         // is stored in re_raw and that entire string becomes the RegEx's unique identifier for the purpose
  12325.         // of finding an entry in the cache.  Technically, this isn't optimal because some options like Study
  12326.         // and aGetPositionsNotSubstrings don't alter the nature of the compiled RegEx.  However, the CPU time
  12327.         // required to strip off some options prior to doing a cache search seems likely to offset much of the
  12328.         // cache's benefit.  So for this reason, as well as rarity and code size issues, this policy seems best.
  12329.         char *re_raw;      // The RegEx's literal string pattern such as "abc.*123".
  12330.         pcre *re_compiled; // The RegEx in compiled form.
  12331.         pcre_extra *extra; // NULL unless a study() was done (and NULL even then if study() didn't find anything).
  12332.         // int pcre_options; // Not currently needed in the cache since options are implicitly inside re_compiled.
  12333.         bool get_positions_not_substrings;
  12334.     };
  12335.  
  12336.     #define PCRE_CACHE_SIZE 100 // Going too high would be counterproductive due to the slowness of linear search (and also the memory utilization of so many compiled RegEx's).
  12337.     static pcre_cache_entry sCache[PCRE_CACHE_SIZE] = {{0}};
  12338.     static int sLastInsert, sLastFound = -1; // -1 indicates "cache empty".
  12339.     int insert_pos; // v1.0.45.03: This is used to avoid updating sLastInsert until an insert actually occurs (it might not occur if a compile error occurs in the regex, or something else stops it early).
  12340.  
  12341.     // CHECK IF THIS REGEX IS ALREADY IN THE CACHE.
  12342.     if (sLastFound == -1) // Cache is empty, so insert this RegEx at the first position.
  12343.         insert_pos = 0;  // A section further below will change sLastFound to be 0.
  12344.     else
  12345.     {
  12346.         // Search the cache to see if it contains the caller-specified RegEx in compiled form.
  12347.         // First check if the last-found item is a match, since often it will be (such as cases
  12348.         // where a script-loop executes only one RegEx, and also for SetTitleMatchMode RegEx).
  12349.         if (!strcmp(aRegEx, sCache[sLastFound].re_raw)) // Match found (case sensitive).
  12350.             goto match_found; // And no need to update sLastFound because it's already set right.
  12351.  
  12352.         // Since above didn't find a match, search outward in both directions from the last-found match.
  12353.         // A bidirectional search is done because consecutively-called regex's tend to be adjacent to each other
  12354.         // in the array, so performance is improved on average (since most of the time when repeating a previously
  12355.         // executed regex, that regex will already be in the cache -- so optimizing the finding behavior is
  12356.         // more important than optimizing the never-found-because-not-cached behavior).
  12357.         bool go_right;
  12358.         int i, item_to_check, left, right;
  12359.         int last_populated_item = (sCache[PCRE_CACHE_SIZE-1].re_compiled) // When the array is full...
  12360.             ? PCRE_CACHE_SIZE - 1  // ...all items must be checked except the one already done earlier.
  12361.             : sLastInsert;         // ...else only the items actually populated need to be checked.
  12362.  
  12363.         for (go_right = true, left = sLastFound, right = sLastFound, i = 0
  12364.             ; i < last_populated_item  // This limits it to exactly the number of items remaining to be checked.
  12365.             ; ++i, go_right = !go_right)
  12366.         {
  12367.             if (go_right) // Proceed rightward in the array.
  12368.             {
  12369.                 right = (right == last_populated_item) ? 0 : right + 1; // Increment or wrap around back to the left side.
  12370.                 item_to_check = right;
  12371.             }
  12372.             else // Proceed leftward.
  12373.             {
  12374.                 left = (left == 0) ? last_populated_item : left - 1; // Decrement or wrap around back to the right side.
  12375.                 item_to_check = left;
  12376.             }
  12377.             if (!strcmp(aRegEx, sCache[item_to_check].re_raw)) // Match found (case sensitive).
  12378.             {
  12379.                 sLastFound = item_to_check;
  12380.                 goto match_found;
  12381.             }
  12382.         }
  12383.  
  12384.         // Since above didn't goto, no match was found nor is one possible.  So just indicate the insert position
  12385.         // for where this RegEx will be put into the cache.
  12386.         // The following formula is for both cache-full and cache-partially-full.  When the cache is full,
  12387.         // it might not be the best possible formula; but it seems pretty good because it takes a round-robin
  12388.         // approach to overwriting/discarding old cache entries.  A discarded entry might have just been
  12389.         // used -- or even be sLastFound itself -- but on average, this approach seems pretty good because a
  12390.         // script loop that uses 50 unique RegEx's will quickly stabilize in the cache so that all 50 of them
  12391.         // stay compiled/cached until the loop ends.
  12392.         insert_pos = (sLastInsert == PCRE_CACHE_SIZE-1) ? 0 : sLastInsert + 1; // Formula works for both full and partially-full array.
  12393.     }
  12394.     // Since the above didn't goto:
  12395.     // - This RegEx isn't yet in the cache.  So compile it and put it in the cache, then return it to caller.
  12396.     // - Above is responsible for having set insert_pos to the cache position where the new RegEx will be stored.
  12397.  
  12398.     // The following macro is for maintainability, to enforce the definition of "default" in multiple places.
  12399.     // PCRE_NEWLINE_CRLF is the default in AutoHotkey rather than PCRE_NEWLINE_LF because *multiline* haystacks
  12400.     // that scripts will use are expected to come from:
  12401.     // 50%: FileRead: Uses `r`n by default, for performance)
  12402.     // 10%: Clipboard: Normally uses `r`n (includes files copied from Explorer, text data, etc.)
  12403.     // 20%: UrlDownloadToFile: Testing shows that it varies: e.g. microsoft.com uses `r`n, but `n is probably
  12404.     //      more common due to FTP programs automatically translating CRLF to LF when uploading to UNIX servers.
  12405.     // 20%: Other sources such as GUI edit controls: It's fairly unusual to want to use RegEx on multiline data
  12406.     //      from GUI controls, but in such case `n is much more common than `r`n.
  12407.     #define SET_DEFAULT_PCRE_OPTIONS \
  12408.     {\
  12409.         pcre_options = PCRE_NEWLINE_CRLF;\
  12410.         aGetPositionsNotSubstrings = false;\
  12411.         do_study = false;\
  12412.     }
  12413.     #define PCRE_NEWLINE_BITS (PCRE_NEWLINE_CRLF | PCRE_NEWLINE_ANY) // Covers all bits that are used for newline options.
  12414.  
  12415.     // SET DEFAULT OPTIONS:
  12416.     int pcre_options;
  12417.     long long do_study;
  12418.     SET_DEFAULT_PCRE_OPTIONS
  12419.  
  12420.     // PARSE THE OPTIONS (if any).
  12421.     char *pat; // When options-parsing is done, pat will point to the start of the pattern itself.
  12422.     for (pat = aRegEx;; ++pat)
  12423.     {
  12424.         switch(*pat)
  12425.         {
  12426.         case 'i': pcre_options |= PCRE_CASELESS;  break;  // Perl-compatible options.
  12427.         case 'm': pcre_options |= PCRE_MULTILINE; break;  //
  12428.         case 's': pcre_options |= PCRE_DOTALL;    break;  //
  12429.         case 'x': pcre_options |= PCRE_EXTENDED;  break;  //
  12430.         case 'A': pcre_options |= PCRE_ANCHORED;  break;      // PCRE-specific options (uppercase used by convention, even internally by PCRE itself).
  12431.         case 'D': pcre_options |= PCRE_DOLLAR_ENDONLY; break; //
  12432.         case 'J': pcre_options |= PCRE_DUPNAMES;       break; //
  12433.         case 'U': pcre_options |= PCRE_UNGREEDY;       break; //
  12434.         case 'X': pcre_options |= PCRE_EXTRA;          break; //
  12435.         case '\a':pcre_options = (pcre_options & ~PCRE_NEWLINE_BITS) | PCRE_NEWLINE_ANY; break; // v1.0.46.06: alert/bell (i.e. `a) is used for PCRE_NEWLINE_ANY.
  12436.         case '\n':pcre_options = (pcre_options & ~PCRE_NEWLINE_BITS) | PCRE_NEWLINE_LF; break; // See below.
  12437.             // Above option: Could alternatively have called it "LF" rather than or in addition to "`n", but that
  12438.             // seems slightly less desirable due to potential overlap/conflict with future option letters,
  12439.             // plus the fact that `n should be pretty well known to AutoHotkey users, especially advanced ones
  12440.             // using RegEx.  Note: `n`r is NOT treated the same as `r`n because there's a slight chance PCRE
  12441.             // will someday support `n`r for some obscure usage (or just for symmetry/completeness).
  12442.             // The PCRE_NEWLINE_XXX options are valid for both compile() and exec(), but specifying it for exec()
  12443.             // would only serve to override the default stored inside the compiled pattern (seems rarely needed).
  12444.         case '\r':
  12445.             if (pat[1] == '\n') // Even though `r`n is the default, it's recognized as an option for flexibility and intuitiveness.
  12446.             {
  12447.                 ++pat; // Skip over the second character so that it's not recognized as a separate option by the next iteration.
  12448.                 pcre_options = (pcre_options & ~PCRE_NEWLINE_BITS) | PCRE_NEWLINE_CRLF; // Set explicitly in case it was unset by an earlier option. Remember that PCRE_NEWLINE_CRLF is a bitwise combination of PCRE_NEWLINE_LF and CR.
  12449.             }
  12450.             else // For completeness, it's easy to support PCRE_NEWLINE_CR too, though nowadays I think it's quite rare (former Macintosh format).
  12451.                 pcre_options = (pcre_options & ~PCRE_NEWLINE_BITS) | PCRE_NEWLINE_CR; // Do it this way because PCRE_NEWLINE_CRLF is a bitwise combination of PCRE_NEWLINE_CR and PCRE_NEWLINE_LF.
  12452.             break;
  12453.  
  12454.         // Other options (uppercase so that lowercase can be reserved for future/PERL options):
  12455.         case 'P': aGetPositionsNotSubstrings = true;   break;
  12456.         case 'S': do_study = true;                     break;
  12457.  
  12458.         case ' ':  // Allow only spaces and tabs as fillers so that everything else is protected/reserved for
  12459.         case '\t': // future use (such as future PERL options).
  12460.             break;
  12461.  
  12462.         case ')': // This character, when not escaped, marks the normal end of the options section.  We know it's not escaped because if it had been, the loop would have stopped at the backslash before getting here.
  12463.             ++pat; // Set pat to be the start of the actual RegEx pattern, and leave options set to how they were by any prior iterations above.
  12464.             goto break_both;
  12465.  
  12466.         default: // Namely the following:
  12467.         //case '\0': No options are present, so ignore any letters that were accidentally recognized and treat entire string as the pattern.
  12468.         //case '(' : An open parenthesis must be considered an invalid option because otherwise it would be ambiguous with a subpattern.
  12469.         //case '\\': In addition to backslash being an invalid option, it also covers "\)" as being invalid (i.e. so that it's never necessary to check for an escaped close-parenthesis).
  12470.         //case all-other-chars: All others are invalid options; so like backslash above, ignore any letters that were accidentally recognized and treat entire string as the pattern.
  12471.             SET_DEFAULT_PCRE_OPTIONS // Revert to original options in case any early letters happened to match valid options.
  12472.             pat = aRegEx; // Indicate that the entire string is the pattern (no options).
  12473.             // To distinguish between a bad option and no options at all (for better error reporting), could check if
  12474.             // within the next few chars there's an unmatched close-parenthesis (non-escaped).  If so, the user
  12475.             // intended some options but one of them was invalid.  However, that would be somewhat difficult to do
  12476.             // because both \) and [)] are valid regex patterns that contain an unmatched close-parenthesis.
  12477.             // Since I don't know for sure if there are other cases (or whether future RegEx extensions might
  12478.             // introduce more such cases), it seems best not to attempt to distinguish.  Using more than two options
  12479.             // is extremely rare anyway, so syntax errors of this type do not happen often (and the only harm done
  12480.             // is a misleading error message from PCRE rather than something like "Bad option").  In addition,
  12481.             // omitting it simplifies the code and slightly improves performance.
  12482.             goto break_both;
  12483.         } // switch(*pat)
  12484.     } // for()
  12485.  
  12486. break_both:
  12487.     // Reaching here means that pat has been set to the beginning of the RegEx pattern itself and all options
  12488.     // are set properly.
  12489.  
  12490.     const char *error_msg;
  12491.     char error_buf[ERRORLEVEL_SAVED_SIZE];
  12492.     int error_code, error_offset;
  12493.     pcre *re_compiled;
  12494.  
  12495.     // COMPILE THE REGEX.
  12496.     if (   !(re_compiled = pcre_compile2(pat, pcre_options, &error_code, &error_msg, &error_offset, NULL))   )
  12497.     {
  12498.         if (aResultToken) // Only when this is non-NULL does caller want ErrorLevel changed.
  12499.         {
  12500.             // Since both the error code and the offset are desirable outputs, it semes best to also
  12501.             // include descriptive error text (debatable).
  12502.             g_ErrorLevel->Assign(error_buf
  12503.                 , snprintf(error_buf, sizeof(error_buf), "Compile error %d at offset %d: %s"
  12504.                     , error_code, error_offset, error_msg));
  12505.         }
  12506.         goto error;
  12507.     }
  12508.  
  12509.     if (do_study)
  12510.     {
  12511.         // Currently, PCRE has no study options so that parameter is always 0.
  12512.         // Calling pcre_study currently adds about 1.5 KB of uncompressed code size; but it seems likely to be
  12513.         // a worthwhile option for complex RegEx's that are executed many times in a loop.
  12514.         aExtra = pcre_study(re_compiled, 0, &error_msg); // aExtra is an output parameter for caller.
  12515.         // Above returns NULL on failure or inability to find anything worthwhile in its study.  NULL is exactly
  12516.         // the right value to pass to exec() to indicate "no study info".
  12517.         // The following isn't done because:
  12518.         // 1) It seems best not to abort the caller's RegEx operation just due to a study error, since the only
  12519.         //    error likely to happen (from looking at PCRE's source code) is out-of-memory.
  12520.         // 2) ErrorLevel is traditioally used for error/abort conditions only, not warnings.  So it seems best
  12521.         //    not to pollute it with a warning message that indicates, "yes it worked, but here's a warning".
  12522.         //    ErrorLevel 0 (success) seems better and more desirable.
  12523.         // 3) Reduced code size.
  12524.         //if (error_msg)
  12525.         //{
  12526.             //if (aResultToken) // Only when this is non-NULL does caller want ErrorLevel changed.
  12527.             //{
  12528.             //    snprintf(error_buf, sizeof(error_buf), "Study error: %s", error_msg);
  12529.             //    g_ErrorLevel->Assign(error_buf);
  12530.             //}
  12531.             //goto error;
  12532.         //}
  12533.     }
  12534.     else // No studying desired.
  12535.         aExtra = NULL; // aExtra is an output parameter for caller.
  12536.  
  12537.     // ADD THE NEWLY-COMPILED REGEX TO THE CACHE.
  12538.     // An earlier stage has set insert_pos to be the desired insert-position in the cache.
  12539.     pcre_cache_entry &this_entry = sCache[insert_pos]; // For performance and convenience.
  12540.     if (this_entry.re_compiled) // An existing cache item is being overwritten, so free it's attributes.
  12541.     {
  12542.         // Free the old cache entry's attributes in preparation for overwriting them with the new one's.
  12543.         free(this_entry.re_raw);           // Free the uncompiled pattern.
  12544.         pcre_free(this_entry.re_compiled); // Free the compiled pattern.
  12545.     }
  12546.     //else the insert-position is an empty slot, which is usually the case because most scripts contain fewer than
  12547.     // PCRE_CACHE_SIZE unique regex's.  Nothing extra needs to be done.
  12548.     this_entry.re_raw = _strdup(aRegEx); // _strdup() is very tiny and basically just calls strlen+malloc+strcpy.
  12549.     this_entry.re_compiled = re_compiled;
  12550.     this_entry.extra = aExtra;
  12551.     this_entry.get_positions_not_substrings = aGetPositionsNotSubstrings;
  12552.     // "this_entry.pcre_options" doesn't exist because it isn't currently needed in the cache.  This is
  12553.     // because the RE's options are implicitly stored inside re_compiled.
  12554.  
  12555.     sLastInsert = insert_pos; // v1.0.45.03: Must be done only *after* the insert succeeded because some things rely on sLastInsert being synonymous with the last populated item in the cache (when the cache isn't yet full).
  12556.     sLastFound = sLastInsert; // Relied upon in the case where sLastFound==-1. But it also sets things up to start the search at this item next time, because it's a bit more likely to be found here such as tight loops containing only one RegEx.
  12557.     // Remember that although sLastFound==sLastInsert in this case, it isn't always so -- namely when a previous
  12558.     // call found an existing match in the cache without having to compile and insert the item.
  12559.  
  12560.     LeaveCriticalSection(&g_CriticalRegExCache);
  12561.     return re_compiled; // Indicate success.
  12562.  
  12563. match_found: // RegEx was found in the cache at position sLastFound, so return the cached info back to the caller.
  12564.     aGetPositionsNotSubstrings = sCache[sLastFound].get_positions_not_substrings;
  12565.     aExtra = sCache[sLastFound].extra;
  12566.  
  12567.     LeaveCriticalSection(&g_CriticalRegExCache);
  12568.     return sCache[sLastFound].re_compiled; // Indicate success.
  12569.  
  12570. error: // Since NULL is returned here, caller should ignore the contents of the output parameters.
  12571.     if (aResultToken)
  12572.     {
  12573.         aResultToken->symbol = SYM_STRING;
  12574.         aResultToken->marker = "";
  12575.     }
  12576.  
  12577.     LeaveCriticalSection(&g_CriticalRegExCache);
  12578.     return NULL; // Indicate failure.
  12579. }
  12580.  
  12581.  
  12582.  
  12583. char *RegExMatch(char *aHaystack, char *aNeedleRegEx)
  12584. // Returns NULL if no match.  Otherwise, returns the address where the pattern was found in aHaystack.
  12585. {
  12586.     bool get_positions_not_substrings; // Currently ignored.
  12587.     pcre_extra *extra;
  12588.     pcre *re;
  12589.  
  12590.     // Compile the regex or get it from cache.
  12591.     if (   !(re = get_compiled_regex(aNeedleRegEx, get_positions_not_substrings, extra, NULL))   ) // Compiling problem.
  12592.         return NULL; // Our callers just want there to be "no match" in this case.
  12593.  
  12594.     // Set up the offset array, which consists of int-pairs containing the start/end offset of each match.
  12595.     // For simplicity, use a fixed size because even if it's too small (unlikely for our types of callers),
  12596.     // PCRE will still operate properly (though it returns 0 to indicate the too-small condition).
  12597.     #define RXM_INT_COUNT 30  // Should be a multiple of 3.
  12598.     int offset[RXM_INT_COUNT];
  12599.  
  12600.     // Execute the regex.
  12601.     int captured_pattern_count = pcre_exec(re, extra, aHaystack, (int)strlen(aHaystack), 0, 0, offset, RXM_INT_COUNT);
  12602.     if (captured_pattern_count < 0) // PCRE_ERROR_NOMATCH or some kind of error.
  12603.         return NULL;
  12604.  
  12605.     // Otherwise, captured_pattern_count>=0 (it's 0 when offset[] was too small; but that's harmless in this case).
  12606.     return aHaystack + offset[0]; // Return the position of the entire-pattern match.
  12607. }
  12608.  
  12609.  
  12610.  
  12611. void RegExReplace(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount
  12612.     , pcre *aRE, pcre_extra *aExtra, char *aHaystack, int aHaystackLength, int aStartingOffset
  12613.     , int aOffset[], int aNumberOfIntsInOffset)
  12614. {
  12615.     // Set default return value in case of early return.
  12616.     aResultToken.symbol = SYM_STRING;
  12617.     aResultToken.marker = aHaystack; // v1.0.46.06: aHaystack vs. "" is the new default because it seems a much safer and more convenient to return aHaystack when an unexpected PCRE-exec error occurs (such an error might otherwise cause loss of data in scripts that don't meticulously check ErrorLevel after each RegExReplace()).
  12618.  
  12619.     // If an output variable was provided for the count, resolve it early in case of early goto.
  12620.     // Fix for v1.0.47.05: In the unlikely event that output_var_count is the same script-variable as
  12621.     // as the haystack, needle, or replacement (i.e. the same memory), don't set output_var_count until
  12622.     // immediately prior to returning.  Otherwise, haystack, needle, or replacement would corrupted while
  12623.     // it's still being used here.
  12624.     Var *output_var_count = (aParamCount > 3 && aParam[3]->symbol == SYM_VAR) ? aParam[3]->var : NULL; // SYM_VAR's Type() is always VAR_NORMAL.
  12625.     int replacement_count = 0; // This value will be stored in output_var_count, but only at the very end due to the reason above.
  12626.  
  12627.     // Get the replacement text (if any) from the incoming parameters.  If it was omitted, treat it as "".
  12628.     char repl_buf[MAX_FORMATTED_NUMBER_LENGTH + 1];
  12629.     char *replacement = (aParamCount > 2) ? ExprTokenToString(*aParam[2], repl_buf) : "";
  12630.  
  12631.     // In PCRE, lengths and such are confined to ints, so there's little reason for using unsigned for anything.
  12632.     int captured_pattern_count, empty_string_is_not_a_match, match_length, ref_num
  12633.         , result_size, new_result_length, haystack_portion_length, second_iteration, substring_name_length
  12634.         , extra_offset, pcre_options;
  12635.     char *haystack_pos, *match_pos, *src, *src_orig, *dest, *closing_brace, char_after_dollar
  12636.         , *substring_name_pos, substring_name[33] // In PCRE, "Names consist of up to 32 alphanumeric characters and underscores."
  12637.         , transform;
  12638.  
  12639.     // Caller has provided a NULL circuit_token as a means of passing back memory we allocate here.
  12640.     // So if we change "result" to be non-NULL, the caller will take over responsibility for freeing that memory.
  12641.     char *&result = (char *&)aResultToken.circuit_token; // Make an alias to type-cast and for convenience.
  12642.     int &result_length = (int &)aResultToken.buf; // MANDATORY FOR USERS OF CIRCUIT_TOKEN: "buf" is being overloaded to store the length for our caller.
  12643.     result_size = 0;   // And caller has already set "result" to be NULL.  The buffer is allocated only upon
  12644.     result_length = 0; // first use to avoid a potentially massive allocation that might be wasted and cause swapping (not to mention that we'll have better ability to estimate the correct total size after the first replacement is discovered).
  12645.  
  12646.     // Below uses a temp variable because realloc() returns NULL on failure but leaves original block allocated.
  12647.     // Note that if it's given a NULL pointer, realloc() does a malloc() instead.
  12648.     char *realloc_temp;
  12649.     #define REGEX_REALLOC(size) \
  12650.     {\
  12651.         result_size = size;\
  12652.         if (   !(realloc_temp = (char *)realloc(result, result_size))   )\
  12653.             goto out_of_mem;\
  12654.         result = realloc_temp;\
  12655.     }
  12656.  
  12657.     // See if a replacement limit was specified.  If not, use the default (-1 means "replace all").
  12658.     int limit = (aParamCount > 4) ? (int)ExprTokenToInt64(*aParam[4]) : -1;
  12659.  
  12660.     // aStartingOffset is altered further on in the loop; but for its initial value, the caller has ensured
  12661.     // that it lies within aHaystackLength.  Also, if there are no replacements yet, haystack_pos ignores
  12662.     // aStartingOffset because otherwise, when the first replacement occurs, any part of haystack that lies
  12663.     // to the left of a caller-specified aStartingOffset wouldn't get copied into the result.
  12664.     for (empty_string_is_not_a_match = 0, haystack_pos = aHaystack
  12665.         ;; haystack_pos = aHaystack + aStartingOffset) // See comment above.
  12666.     {
  12667.         // Execute the expression to find the next match.
  12668.         captured_pattern_count = (limit == 0) ? PCRE_ERROR_NOMATCH // Only when limit is exactly 0 are we done replacing.  All negative values are "replace all".
  12669.             : pcre_exec(aRE, aExtra, aHaystack, (int)aHaystackLength, aStartingOffset
  12670.                 , empty_string_is_not_a_match, aOffset, aNumberOfIntsInOffset);
  12671.  
  12672.         if (captured_pattern_count == PCRE_ERROR_NOMATCH)
  12673.         {
  12674.             if (empty_string_is_not_a_match && aStartingOffset < aHaystackLength && limit != 0) // replacement_count>0 whenever empty_string_is_not_a_match!=0.
  12675.             {
  12676.                 // This situation happens when a previous iteration found a match but it was the empty string.
  12677.                 // That iteration told the pcre_exec that just occurred above to try to match something other than ""
  12678.                 // at the same position.  But since we're here, it wasn't able to find such a match.  So just copy
  12679.                 // the current character over literally then advance to the next character to resume normal searching.
  12680.                 empty_string_is_not_a_match = 0; // Reset so that the next iteration starts off with the normal matching method.
  12681.                 result[result_length++] = *haystack_pos; // This can't overflow because the size calculations in a previous iteration reserved 3 bytes: 1 for this character, 1 for the possible LF that follows CR, and 1 for the terminator.
  12682.                 ++aStartingOffset; // Advance to next candidate section of haystack.
  12683.                 // v1.0.46.06: This following section was added to avoid finding a match between a CR and LF
  12684.                 // when PCRE_NEWLINE_ANY mode is in effect.  The fact that this is the only change for
  12685.                 // PCRE_NEWLINE_ANY relies on the belief that any pattern that matches the empty string in between
  12686.                 // a CR and LF must also match the empty string that occurs right before the CRLF (even if that
  12687.                 // pattern also matched a non-empty string right before the empty one in front of the CRLF).  If
  12688.                 // this belief is correct, no logic similar to this is needed near the bottom of the main loop
  12689.                 // because the empty string found immediately prior to this CRLF will put us into
  12690.                 // empty_string_is_not_a_match mode, which will then execute this section of code (unless
  12691.                 // empty_string_is_not_a_match mode actually found a match, in which case the logic here seems
  12692.                 // superseded by that match?)  Even if this reasoning is not a complete solution, it might be
  12693.                 // adequate if patterns that match empty strings are rare, which I believe they are.  In fact,
  12694.                 // they might be so rare that arguably this could be documented as a known limitation rather than
  12695.                 // having added the following section of code in the first place.
  12696.                 // Examples that illustrate the effect:
  12697.                 //    MsgBox % "<" . RegExReplace("`r`n", "`a).*", "xxx") . ">"
  12698.                 //    MsgBox % "<" . RegExReplace("`r`n", "`am)^.*$", "xxx") . ">"
  12699.                 if (*haystack_pos == '\r' && haystack_pos[1] == '\n')
  12700.                 {
  12701.                     // pcre_fullinfo() is a fast call, so it's called every time to simplify the code (I don't think
  12702.                     // this whole "empty_string_is_not_a_match" section of code executes for most patterns anyway,
  12703.                     // so performance seems less of a concern).
  12704.                     if (!pcre_fullinfo(aRE, aExtra, PCRE_INFO_OPTIONS, &pcre_options) // Success.
  12705.                         && (pcre_options & PCRE_NEWLINE_ANY)) // See comment above.
  12706.                     {
  12707.                         result[result_length++] = '\n'; // This can't overflow because the size calculations in a previous iteration reserved 3 bytes: 1 for this character, 1 for the possible LF that follows CR, and 1 for the terminator.
  12708.                         ++aStartingOffset; // Skip over this LF because it "belongs to" the CR that preceded it.
  12709.                     }
  12710.                 }
  12711.                 continue; // i.e. we're not done yet because the "no match" above was a special one and there's still more haystack to check.
  12712.             }
  12713.             // Otherwise, there aren't any more matches.  So we're all done except for copying the last part of
  12714.             // haystack into the result (if applicable).
  12715.             if (replacement_count) // And by definition, result!=NULL due in this case to prior iterations.
  12716.             {
  12717.                 if (haystack_portion_length = aHaystackLength - aStartingOffset) // This is the remaining part of haystack that needs to be copied over as-is.
  12718.                 {
  12719.                     new_result_length = result_length + haystack_portion_length;
  12720.                     if (new_result_length >= result_size)
  12721.                         REGEX_REALLOC(new_result_length + 1); // This will end the loop if an alloc error occurs.
  12722.                     memcpy(result + result_length, haystack_pos, haystack_portion_length); // memcpy() usually benches a little faster than strcpy().
  12723.                     result_length = new_result_length; // Remember that result_length is actually an output for our caller, so even if for no other reason, it must be kept accurate for that.
  12724.                 }
  12725.                 result[result_length] = '\0'; // result!=NULL when replacement_count!=0.  Also, must terminate it unconditionally because other sections usually don't do it.
  12726.                 // Set RegExMatch()'s return value to be "result":
  12727.                 aResultToken.marker = result;  // Caller will take care of freeing result's memory.
  12728.             }
  12729.             // Section below is obsolete but is retained for its comments.
  12730.             //else // No replacements were actually done, so just return the original string to avoid malloc+memcpy
  12731.             // (in addition, returning the original might help the caller make other optimizations).
  12732.             //{
  12733.                 // Already set as a default earlier, so commented out:
  12734.                 //aResultToken.marker = aHaystack;
  12735.                 // Not necessary to set output-var (length) for caller except when we allocated memory for the caller:
  12736.                 //result_length = aHaystackLength; // result_length is an alias for an output parameter, so update for maintainability even though currently callers don't use it when no alloc of circuit_token.
  12737.                 //
  12738.                 // There's no need to do the following because it should already be that way when replacement_count==0.
  12739.                 //if (result)
  12740.                 //    free(result);
  12741.                 //result = NULL; // This tells the caller that we already freed it (i.e. from its POV, we never allocated anything).
  12742.             //}
  12743.  
  12744.             g_ErrorLevel->Assign(ERRORLEVEL_NONE); // All done, indicate success via ErrorLevel.
  12745.             goto set_count_and_return;             //
  12746.         }
  12747.  
  12748.         // Otherwise:
  12749.         if (captured_pattern_count < 0) // An error other than "no match". These seem very rare, so it seems best to abort rather than yielding a partially-converted result.
  12750.         {
  12751.             g_ErrorLevel->Assign(captured_pattern_count); // No error text is stored; just a negative integer (since these errors are pretty rare).
  12752.             goto set_count_and_return; // Goto vs. break to leave aResultToken.marker set to aHaystack and replacement_count set to 0, and let ErrorLevel tell the story.
  12753.         }
  12754.  
  12755.         // Otherwise (since above didn't return, break, or continue), a match has been found (i.e. captured_pattern_count > 0;
  12756.         // it should never be 0 in this case because that only happens when offset[] is too small, which it isn't).
  12757.         ++replacement_count;
  12758.         --limit; // It's okay if it goes below -1 because all negatives are treated as "replace all".
  12759.         match_pos = aHaystack + aOffset[0]; // This is the location in aHaystack of the entire-pattern match.
  12760.         haystack_portion_length = (int)(match_pos - haystack_pos); // The length of the haystack section between the end of the previous match and the start of the current one.
  12761.  
  12762.         // Handle this replacement by making two passes through the replacement-text: The first calculates the size
  12763.         // (which avoids having to constantly check for buffer overflow with potential realloc at multiple stages).
  12764.         // The second iteration copies the replacement (along with any literal text in haystack before it) into the
  12765.         // result buffer (which was expanded if necessary by the first iteration).
  12766.         for (second_iteration = 0; second_iteration < 2; ++second_iteration) // second_iteration is used as a boolean for readability.
  12767.         {
  12768.             if (second_iteration)
  12769.             {
  12770.                 // Using the required length calculated by the first iteration, expand/realloc "result" if necessary.
  12771.                 if (new_result_length + 3 > result_size) // Must use +3 not +1 in case of empty_string_is_not_a_match (which needs room for up to two extra characters).
  12772.                 {
  12773.                     // The first expression passed to REGEX_REALLOC is the average length of each replacement so far.
  12774.                     // It's more typically more accurate to pass that than the following "length of current
  12775.                     // replacement":
  12776.                     //    new_result_length - haystack_portion_length - (aOffset[1] - aOffset[0])
  12777.                     // Above is the length difference between the current replacement text and what it's
  12778.                     // replacing (it's negative when replacement is smaller than what it replaces).
  12779.                     REGEX_REALLOC(PredictReplacementSize((new_result_length - aOffset[1]) / replacement_count // See above.
  12780.                         , replacement_count, limit, aHaystackLength, new_result_length+2, aOffset[1])); // +2 in case of empty_string_is_not_a_match (which needs room for up to two extra characters).  The function will also do another +1 to convert length to size (for terminator).
  12781.                     // The above will return if an alloc error occurs.
  12782.                 }
  12783.                 //else result_size is not only large enough, but also non-zero.  Other sections rely on it always
  12784.                 // being non-zero when replacement_count>0.
  12785.  
  12786.                 // Before doing the actual replacement and its backreferences, copy over the part of haystack that
  12787.                 // appears before the match.
  12788.                 if (haystack_portion_length)
  12789.                 {
  12790.                     memcpy(result + result_length, haystack_pos, haystack_portion_length);
  12791.                     result_length += haystack_portion_length;
  12792.                 }
  12793.                 dest = result + result_length; // Init dest for use by the loops further below.
  12794.             }
  12795.             else // i.e. it's the first iteration, so begin calculating the size required.
  12796.                 new_result_length = result_length + haystack_portion_length; // Init length to the part of haystack before the match (it must be copied over as literal text).
  12797.  
  12798.             // DOLLAR SIGN ($) is the only method supported because it simplifies the code, improves performance,
  12799.             // and avoids the need to escape anything other than $ (which simplifies the syntax).
  12800.             for (src = replacement; ; ++src)  // For each '$' (increment to skip over the symbol just found by the inner for()).
  12801.             {
  12802.                 // Find the next '$', if any.
  12803.                 src_orig = src; // Init once for both loops below.
  12804.                 if (second_iteration) // Mode: copy src-to-dest.
  12805.                 {
  12806.                     while (*src && *src != '$') // While looking for the next '$', copy over everything up until the '$'.
  12807.                         *dest++ = *src++;
  12808.                     result_length += (int)(src - src_orig);
  12809.                 }
  12810.                 else // This is the first iteration (mode: size-calculation).
  12811.                 {
  12812.                     for (; *src && *src != '$'; ++src); // Find the next '$', if any.
  12813.                     new_result_length += (int)(src - src_orig); // '$' or '\0' was found: same expansion either way.
  12814.                 }
  12815.                 if (!*src)  // Reached the end of the replacement text.
  12816.                     break;  // Nothing left to do, so if this is the first major iteration, begin the second.
  12817.  
  12818.                 // Otherwise, a '$' has been found.  Check if it's a backreference and handle it.
  12819.                 // But first process any special flags that are present.
  12820.                 transform = '\0'; // Set default. Indicate "no transformation".
  12821.                 extra_offset = 0; // Set default. Indicate that there's no need to hop over an extra character.
  12822.                 if (char_after_dollar = src[1]) // This check avoids calling toupper on '\0', which directly or indirectly causes an assertion error in CRT.
  12823.                 {
  12824.                     switch(char_after_dollar = toupper(char_after_dollar))
  12825.                     {
  12826.                     case 'U':
  12827.                     case 'L':
  12828.                     case 'T':
  12829.                         transform = char_after_dollar;
  12830.                         extra_offset = 1;
  12831.                         char_after_dollar = src[2]; // Ignore the transform character for the purposes of backreference recognition further below.
  12832.                         break;
  12833.                     //else leave things at their defaults.
  12834.                     }
  12835.                 }
  12836.                 //else leave things at their defaults.
  12837.  
  12838.                 ref_num = INT_MIN; // Set default to "no valid backreference".  Use INT_MIN to virtually guaranty that anything other than INT_MIN means that something like a backreference was found (even if it's invalid, such as ${-5}).
  12839.                 switch (char_after_dollar)
  12840.                 {
  12841.                 case '{':  // Found a backreference: ${...
  12842.                     substring_name_pos = src + 2 + extra_offset;
  12843.                     if (closing_brace = strchr(substring_name_pos, '}'))
  12844.                     {
  12845.                         if (substring_name_length = (int)(closing_brace - substring_name_pos))
  12846.                         {
  12847.                             if (substring_name_length < sizeof(substring_name))
  12848.                             {
  12849.                                 strlcpy(substring_name, substring_name_pos, substring_name_length + 1); // +1 to convert length to size, which truncates the new string at the desired position.
  12850.                                 if (IsPureNumeric(substring_name, true, false, true)) // Seems best to allow floating point such as 1.0 because it will then get truncated to an integer.  It seems to rare that anyone would want to use floats as names.
  12851.                                     ref_num = atoi(substring_name); // Uses atoi() vs. ATOI to avoid potential overlap with non-numeric names such as ${0x5}, which should probably be considered a name not a number?  In other words, seems best not to make some names that start with numbers "special" just because they happen to be hex numbers.
  12852.                                 else // For simplicity, no checking is done to ensure it consiss of the "32 alphanumeric characters and underscores".  Let pcre_get_stringnumber() figure that out for us.
  12853.                                     ref_num = pcre_get_stringnumber(aRE, substring_name); // Returns a negative on failure, which when stored in ref_num is relied upon as an inticator.
  12854.                             }
  12855.                             //else it's too long, so it seems best (debatable) to treat it as a unmatched/unfound name, i.e. "".
  12856.                             src = closing_brace; // Set things up for the next iteration to resume at the char after "${..}"
  12857.                         }
  12858.                         //else it's ${}, so do nothing, which in effect will treat it all as literal text.
  12859.                     }
  12860.                     //else unclosed '{': for simplicity, do nothing, which in effect will treat it all as literal text.
  12861.                     break;
  12862.  
  12863.                 case '$':  // i.e. Two consecutive $ amounts to one literal $.
  12864.                     ++src; // Skip over the first '$', and the loop's increment will skip over the second. "extra_offset" is ignored due to rarity and silliness.  Just transcribe things like $U$ as U$ to indicate the problem.
  12865.                     break; // This also sets up things properly to copy a single literal '$' into the result.
  12866.  
  12867.                 case '\0': // i.e. a single $ was found at the end of the string.
  12868.                     break; // Seems best to treat it as literal (strictly speaking the script should have escaped it).
  12869.  
  12870.                 default:
  12871.                     if (char_after_dollar >= '0' && char_after_dollar <= '9') // Treat it as a single-digit backreference. CONSEQUENTLY, $15 is really $1 followed by a literal '5'.
  12872.                     {
  12873.                         ref_num = char_after_dollar - '0'; // $0 is the whole pattern rather than a subpattern.
  12874.                         src += 1 + extra_offset; // Set things up for the next iteration to resume at the char after $d. Consequently, $19 is seen as $1 followed by a literal 9.
  12875.                     }
  12876.                     //else not a digit: do nothing, which treats a $x as literal text (seems ok since like $19, $name will never be supported due to ambiguity; only ${name}).
  12877.                 } // switch (char_after_dollar)
  12878.  
  12879.                 if (ref_num == INT_MIN) // Nothing that looks like backreference is present (or the very unlikely ${-2147483648}).
  12880.                 {
  12881.                     if (second_iteration)
  12882.                     {
  12883.                         *dest++ = *src;  // src is incremented by the loop.  Copy only one character because the enclosing loop will take care of copying the rest.
  12884.                         ++result_length; // Update the actual length.
  12885.                     }
  12886.                     else
  12887.                         ++new_result_length; // Update the calculated length.
  12888.                     // And now the enclosing loop will take care of the characters beyond src.
  12889.                 }
  12890.                 else // Something that looks like a backreference was found, even if it's invalid (e.g. ${-5}).
  12891.                 {
  12892.                     // It seems to improve convenience and flexibility to transcribe a nonexistent backreference
  12893.                     // as a "" rather than literally (e.g. putting a ${1} literally into the new string).  Although
  12894.                     // putting it in literally has the advantage of helping debugging, it doesn't seem to outweigh
  12895.                     // the convenience of being able to specify nonexistent subpatterns. MORE IMPORANTLY a subpattern
  12896.                     // might not exist per se if it hasn't been matched, such as an "or" like (abc)|(xyz), at least
  12897.                     // when it's the last subpattern, in which case it should definitely be treated as "" and not
  12898.                     // copied over literally.  So that would have to be checked for if this is changed.
  12899.                     if (ref_num >= 0 && ref_num < captured_pattern_count) // Treat ref_num==0 as reference to the entire-pattern's match.
  12900.                     {
  12901.                         if (match_length = aOffset[ref_num*2 + 1] - aOffset[ref_num*2])
  12902.                         {
  12903.                             if (second_iteration)
  12904.                             {
  12905.                                 memcpy(dest, aHaystack + aOffset[ref_num*2], match_length);
  12906.                                 if (transform)
  12907.                                 {
  12908.                                     dest[match_length] = '\0'; // Terminate for use below (shouldn't cause overflow because REALLOC reserved space for terminator; nor should there be any need to undo the termination afterward).
  12909.                                     switch(transform)
  12910.                                     {
  12911.                                     case 'U': CharUpper(dest); break;
  12912.                                     case 'L': CharLower(dest); break;
  12913.                                     case 'T': StrToTitleCase(dest); break;
  12914.                                     }
  12915.                                 }
  12916.                                 dest += match_length;
  12917.                                 result_length += match_length;
  12918.                             }
  12919.                             else // First iteration.
  12920.                                 new_result_length += match_length;
  12921.                         }
  12922.                     }
  12923.                     //else subpattern doesn't exist (or its invalid such as ${-5}, so treat it as blank because:
  12924.                     // 1) It's boosts script flexibility and convenience (at the cost of making it hard to detect
  12925.                     //    script bugs, which would be assisted by transcribing ${999} as literal text rather than "").
  12926.                     // 2) It simplifies the code.
  12927.                     // 3) A subpattern might not exist per se if it hasn't been matched, such as "(abc)|(xyz)"
  12928.                     //    (in which case only one of them is matched).  If such a thing occurs at the end
  12929.                     //    of the RegEx pattern, captured_pattern_count might not include it.  But it seems
  12930.                     //    pretty clear that it should be treated as "" rather than some kind of error condition.
  12931.                 }
  12932.             } // for() (for each '$')
  12933.         } // for() (a 2-iteration for-loop)
  12934.  
  12935.         // If we're here, a match was found.
  12936.         // Technique and comments from pcredemo.c:
  12937.         // If the previous match was NOT for an empty string, we can just start the next match at the end
  12938.         // of the previous one.
  12939.         // If the previous match WAS for an empty string, we can't do that, as it would lead to an
  12940.         // infinite loop. Instead, a special call of pcre_exec() is made with the PCRE_NOTEMPTY and
  12941.         // PCRE_ANCHORED flags set. The first of these tells PCRE that an empty string is not a valid match;
  12942.         // other possibilities must be tried. The second flag restricts PCRE to one match attempt at the
  12943.         // initial string position. If this match succeeds, an alternative to the empty string match has been
  12944.         // found, and we can proceed round the loop.
  12945.         //
  12946.         // The following may be one example of this concept:
  12947.         // In the string "xy", replace the pattern "x?" by "z".  The traditional/proper answer (achieved by
  12948.         // the logic here) is "zzyz" because: 1) The first x is replaced by z; 2) The empty string before y
  12949.         // is replaced by z; 3) the logic here applies PCRE_NOTEMPTY to search again at the same position, but
  12950.         // that search doesn't find a match; so the logic higher above advances to the next character (y) and
  12951.         // continues the search; it finds the empty string at the end of haystack, which is replaced by z.
  12952.         // On the other hand, maybe there's a better example than the above that explains what would happen
  12953.         // if PCRE_NOTEMPTY actually finds a match, or what would happen if this PCRE_NOTEMPTY method weren't
  12954.         // used at all (i.e. infinite loop as mentioned in the previous paragraph).
  12955.         // 
  12956.         // If this match is "" (length 0), then by definitition we just found a match in normal mode, not
  12957.         // PCRE_NOTEMPTY mode (since that mode isn't capable of finding "").  Thus, empty_string_is_not_a_match
  12958.         // is currently 0.
  12959.         if (aOffset[0] == aOffset[1]) // A match was found but it's "".
  12960.             empty_string_is_not_a_match = PCRE_NOTEMPTY | PCRE_ANCHORED; // Try to find a non-"" match at the same position by switching to an alternate mode and doing another iteration.
  12961.         //else not an empty match, so advance to next candidate section of haystack and resume searching.
  12962.         aStartingOffset = aOffset[1]; // In either case, set starting offset to the candidate for the next search.
  12963.     } // for()
  12964.  
  12965.     // All paths above should return (or goto some other label), so execution should never reach here except
  12966.     // through goto:
  12967. out_of_mem:
  12968.     // Due to extreme rarity and since this is a regex execution error of sorts, use PCRE's own error code.
  12969.     g_ErrorLevel->Assign(PCRE_ERROR_NOMEMORY);
  12970.     if (result)
  12971.     {
  12972.         free(result);  // Since result is probably an non-terminated string (not to mention an incompletely created result), it seems best to free it here to remove it from any further consideration by the caller.
  12973.         result = NULL; // Tell caller that it was freed.
  12974.         // AND LEAVE aResultToken.marker (i.e. the final result) set to aHaystack, because the altered result is
  12975.         // indeterminate and thus discarded.
  12976.     }
  12977.     // Now fall through to below so that count is set even for out-of-memory error.
  12978. set_count_and_return:
  12979.     if (output_var_count)
  12980.         output_var_count->Assign(replacement_count); // v1.0.47.05: Must be done last in case output_var_count shares the same memory with haystack, needle, or replacement.
  12981. }
  12982.  
  12983.  
  12984.  
  12985. void BIF_RegEx(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  12986. // This function is the initial entry point for both RegExMatch() and RegExReplace().
  12987. // Caller has set aResultToken.symbol to a default of SYM_INTEGER.
  12988. {
  12989.     bool mode_is_replace = toupper(aResultToken.marker[5]) == 'R'; // Union's marker initially contains the function name; e.g. RegEx[R]eplace.
  12990.     char *needle = ExprTokenToString(*aParam[1], aResultToken.buf); // Load-time validation has already ensured that at least two actual parameters are present.
  12991.  
  12992.     bool get_positions_not_substrings;
  12993.     pcre_extra *extra;
  12994.     pcre *re;
  12995.  
  12996.     // COMPILE THE REGEX OR GET IT FROM CACHE.
  12997.     if (   !(re = get_compiled_regex(needle, get_positions_not_substrings, extra, &aResultToken))   ) // Compiling problem.
  12998.         return; // It already set ErrorLevel and aResultToken for us. If caller provided an output var/array, it is not changed under these conditions because there's no way of knowing how many subpatterns are in the RegEx, and thus no way of knowing how far to init the array.
  12999.  
  13000.     // Since compiling succeeded, get info about other parameters.
  13001.     char haystack_buf[MAX_FORMATTED_NUMBER_LENGTH + 1];
  13002.     char *haystack = ExprTokenToString(*aParam[0], haystack_buf); // Load-time validation has already ensured that at least two actual parameters are present.
  13003.     int haystack_length = (int)EXPR_TOKEN_LENGTH(aParam[0], haystack);
  13004.  
  13005.     int param_index = mode_is_replace ? 5 : 3;
  13006.     int starting_offset;
  13007.     if (aParamCount <= param_index)
  13008.         starting_offset = 0; // The one-based starting position in haystack (if any).  Convert it to zero-based.
  13009.     else
  13010.     {
  13011.         starting_offset = (int)ExprTokenToInt64(*aParam[param_index]) - 1;
  13012.         if (starting_offset < 0) // Same convention as SubStr(): Treat a StartingPos of 0 (offset -1) as "start at the string's last char".  Similarly, treat negatives as starting further to the left of the end of the string.
  13013.         {
  13014.             starting_offset += haystack_length;
  13015.             if (starting_offset < 0)
  13016.                 starting_offset = 0;
  13017.         }
  13018.         else if (starting_offset > haystack_length)
  13019.             // Although pcre_exec() seems to work properly even without this check, its absence would allow
  13020.             // the empty string to be found beyond the length of haystack, which could lead to problems and is
  13021.             // probably more trouble than its worth (assuming it has any worth -- perhaps for a pattern that
  13022.             // looks backward from itself; but that seems too rare to support and might create code that's
  13023.             // harder to maintain, especially in RegExReplace()).
  13024.             starting_offset = haystack_length; // Due to rarity of this condition, opt for simplicity: just point it to the terminator, which is in essence an empty string (which will cause result in "no match" except when searcing for "").
  13025.     }
  13026.  
  13027.     // SET UP THE OFFSET ARRAY, which consists of int-pairs containing the start/end offset of each match.
  13028.     int pattern_count;
  13029.     pcre_fullinfo(re, extra, PCRE_INFO_CAPTURECOUNT, &pattern_count); // The number of capturing subpatterns (i.e. all except (?:xxx) I think). Failure is not checked because it seems too unlikely in this case.
  13030.     ++pattern_count; // Increment to include room for the entire-pattern match.
  13031.     int number_of_ints_in_offset = pattern_count * 3; // PCRE uses 3 ints for each (sub)pattern: 2 for offsets and 1 for its internal use.
  13032.     int *offset = (int *)_alloca(number_of_ints_in_offset * sizeof(int)); // _alloca() boosts performance and seems safe because subpattern_count would usually have to be ridiculously high to cause a stack overflow.
  13033.  
  13034.     if (mode_is_replace) // Handle RegExReplace() completely then return.
  13035.     {
  13036.         RegExReplace(aResultToken, aParam, aParamCount
  13037.             , re, extra, haystack, haystack_length, starting_offset, offset, number_of_ints_in_offset);
  13038.         return;
  13039.     }
  13040.  
  13041.     // OTHERWISE, THIS IS RegExMatch() not RegExReplace().
  13042.     // EXECUTE THE REGEX.
  13043.     int captured_pattern_count = pcre_exec(re, extra, haystack, haystack_length, starting_offset, 0, offset, number_of_ints_in_offset);
  13044.  
  13045.     // SET THE RETURN VALUE AND ERRORLEVEL BASED ON THE RESULTS OF EXECUTING THE EXPRESSION.
  13046.     if (captured_pattern_count == PCRE_ERROR_NOMATCH)
  13047.     {
  13048.         g_ErrorLevel->Assign(ERRORLEVEL_NONE); // i.e. "no match" isn't an error.
  13049.         aResultToken.value_int64 = 0;
  13050.         // BUT CONTINUE ON so that the output-array (if any) is fully reset (made blank), which improves
  13051.         // convenience for the script.
  13052.     }
  13053.     else if (captured_pattern_count < 0) // An error other than "no match".
  13054.     {
  13055.         g_ErrorLevel->Assign(captured_pattern_count); // No error text is stored; just a negative integer (since these errors are pretty rare).
  13056.         aResultToken.symbol = SYM_STRING;
  13057.         aResultToken.marker = "";
  13058.     }
  13059.     else // Match found, and captured_pattern_count <= 0 (but should never be 0 in this case because that only happens when offset[] is too small, which it isn't).
  13060.     {
  13061.         g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  13062.         aResultToken.value_int64 = offset[0] + 1; // i.e. the position of the entire-pattern match is the function's return value.
  13063.     }
  13064.  
  13065.     if (aParamCount < 3 || aParam[2]->symbol != SYM_VAR) // No output var, so nothing more to do.
  13066.         return;
  13067.  
  13068.     // OTHERWISE, THE CALLER PROVIDED AN OUTPUT VAR/ARRAY: Store the substrings that matched the patterns.
  13069.     Var &output_var = *aParam[2]->var; // SYM_VAR's Type() is always VAR_NORMAL.
  13070.     char *mem_to_free = NULL; // Set default.
  13071.  
  13072.     if (get_positions_not_substrings) // In this mode, it's done this way to avoid creating an array if there are no subpatterns; i.e. the return value is the starting position and the array name will contain the length of what was found.
  13073.         output_var.Assign(captured_pattern_count < 0 ? 0 : offset[1] - offset[0]); // Seems better to store length of zero rather than something non-length like -1 (after all, the return value is blank in this case, which should be used as the error indicator).
  13074.     else
  13075.     {
  13076.         if (captured_pattern_count < 0) // Failed or no match.
  13077.             output_var.Assign(); // Make the full-pattern substring blank as a further indicator, and for convenience consistency in the script.
  13078.         else // Greater than 0 (it can't be equal to zero because offset[] was definitely large enough).
  13079.         {
  13080.             // Fix for v1.0.45.07: The following check allow haystack to be the same script-variable as the
  13081.             // output-var/array.  Unless a copy of haystack is made, any subpatterns to be populated after the
  13082.             // entire-pattern output-var below would be corrupted.  In other words, anything that refers to the
  13083.             // contents of haystack after the output-var has been assigned would otherwise refer to the wrong
  13084.             // string.  Note that the following isn't done for the get_positions_not_substrings mode higher above
  13085.             // because that mode never refers to haystack when populating its subpatterns.
  13086.             if (pattern_count > 1 && haystack == output_var.Contents()) // i.e. there are subpatterns to be output afterward, and haystack is the same variable as the output-var that's about to be overwritten below.
  13087.                 if (mem_to_free = _strdup(haystack)) // _strdup() is very tiny and basically just calls strlen+malloc+strcpy.
  13088.                     haystack = mem_to_free;
  13089.                 //else due to the extreme rarity of running out of memory AND SIMULTANEOUSLY having output-var match
  13090.                 // haystack, continue on so that at least partial success is achieved (the only thing that will
  13091.                 // be wrong in this case is the subpatterns, if any).
  13092.             output_var.Assign(haystack + offset[0], offset[1] - offset[0]); // It shouldn't be possible for the full-pattern match's offset to be -1, since if where here, a match on the full pattern was always found.
  13093.         }
  13094.     }
  13095.  
  13096.     if (pattern_count < 2) // There are no subpatterns (only the main pattern), so nothing more to do.
  13097.         goto free_and_return;
  13098.  
  13099.     // OTHERWISE, CONTINUE ON TO STORE THE SUBSTRINGS THAT MATCHED THE SUBPATTERNS (EVEN IF PCRE_ERROR_NOMATCH).
  13100.     // For lookup performance, create a table of subpattern names indexed by subpattern number.
  13101.     char **subpat_name = NULL; // Set default as "no subpattern names present or available".
  13102.     bool allow_dupe_subpat_names = false; // Set default.
  13103.     char *name_table;
  13104.     int name_count, name_entry_size;
  13105.     if (   !pcre_fullinfo(re, extra, PCRE_INFO_NAMECOUNT, &name_count) // Success. Fix for v1.0.45.01: Don't check captured_pattern_count>=0 because PCRE_ERROR_NOMATCH can still have named patterns!
  13106.         && name_count // There's at least one named subpattern.  Relies on short-circuit boolean order.
  13107.         && !pcre_fullinfo(re, extra, PCRE_INFO_NAMETABLE, &name_table) // Success.
  13108.         && !pcre_fullinfo(re, extra, PCRE_INFO_NAMEENTRYSIZE, &name_entry_size)   ) // Success.
  13109.     {
  13110.         int pcre_options;
  13111.         if (!pcre_fullinfo(re, extra, PCRE_INFO_OPTIONS, &pcre_options)) // Success.
  13112.             allow_dupe_subpat_names = pcre_options & PCRE_DUPNAMES;
  13113.         // For indexing simplicity, also include an entry for the main/entire pattern at index 0 even though
  13114.         // it's never used because the entire pattern can't have a name without enclosing it in parentheses
  13115.         // (in which case it's not the entire pattern anymore, but in fact subpattern #1).
  13116.         size_t subpat_array_size = pattern_count * sizeof(char *);
  13117.         subpat_name = (char **)_alloca(subpat_array_size); // See other use of _alloca() above for reasons why it's used.
  13118.         ZeroMemory(subpat_name, subpat_array_size); // Set default for each index to be "no name corresponds to this subpattern number".
  13119.         for (int i = 0; i < name_count; ++i, name_table += name_entry_size)
  13120.         {
  13121.             // Below converts first two bytes of each name-table entry into the pattern number (it might be
  13122.             // possible to simplify this, but I'm not sure if big vs. little-endian will ever be a concern).
  13123.             subpat_name[(name_table[0] << 8) + name_table[1]] = name_table + 2; // For indexing simplicity, subpat_name[0] is for the main/entire pattern though it is never actually used for that because it can't be named without being enclosed in parentheses (in which case it becomes a subpattern).
  13124.             // For simplicity and unlike PHP, IsPureNumeric() isn't called to forbid numeric subpattern names.
  13125.             // It seems the worst than could happen if it is numeric is that it would overlap/overwrite some of
  13126.             // the numerically-indexed elements in the output-array.  Seems pretty harmless given the rarity.
  13127.         }
  13128.     }
  13129.     //else one of the pcre_fullinfo() calls may have failed.  The PCRE docs indicate that this realistically never
  13130.     // happens unless bad inputs were given.  So due to rarity, just leave subpat_name==NULL; i.e. "no named subpatterns".
  13131.  
  13132.     // Make var_name longer than Max so that FindOrAddVar() will be able to spot and report var names
  13133.     // that are too long, either because the base-name is too long, or the name becomes too long
  13134.     // as a result of appending the array index number:
  13135.     char var_name[MAX_VAR_NAME_LENGTH + 68]; // Allow +3 extra for "Len" and "Pos" suffixes, +1 for terminator, and +64 for largest sub-pattern name (actually it's 32, but 64 allows room for future expansion).  64 is also enough room for the largest 64-bit integer, 20 chars: 18446744073709551616
  13136.     strcpy(var_name, output_var.mName); // This prefix is copied in only once, for performance.
  13137.     size_t suffix_length, prefix_length = strlen(var_name);
  13138.     char *var_name_suffix = var_name + prefix_length; // The position at which to copy the sequence number (index).
  13139.     int always_use = output_var.IsLocal() ? ALWAYS_USE_LOCAL : ALWAYS_USE_GLOBAL;
  13140.     int n, p = 1, *this_offset = offset + 2; // Init for both loops below.
  13141.     Var *array_item;
  13142.     bool subpat_not_matched;
  13143.  
  13144.     if (get_positions_not_substrings)
  13145.     {
  13146.         int subpat_pos, subpat_len;
  13147.         for (; p < pattern_count; ++p, this_offset += 2) // Start at 1 because above already did pattern #0 (the full pattern).
  13148.         {
  13149.             subpat_not_matched = (p >= captured_pattern_count || this_offset[0] < 0); // See comments in similar section below about this.
  13150.             if (subpat_not_matched)
  13151.             {
  13152.                 subpat_pos = 0;
  13153.                 subpat_len = 0;
  13154.             }
  13155.             else // NOTE: The formulas below work even for a capturing subpattern that wasn't actually matched, such as one of the following: (abc)|(123)
  13156.             {
  13157.                 subpat_pos = this_offset[0] + 1; // One-based (i.e. position zero means "not found").
  13158.                 subpat_len = this_offset[1] - this_offset[0]; // It seemed more convenient for scripts to store Length instead of an ending offset.
  13159.             }
  13160.  
  13161.             if (subpat_name && subpat_name[p]) // This subpattern number has a name, so store it under that name.
  13162.             {
  13163.                 if (*subpat_name[p]) // This check supports allow_dupe_subpat_names. See comments below.
  13164.                 {
  13165.                     suffix_length = sprintf(var_name_suffix, "Pos%s", subpat_name[p]); // Append the subpattern to the array's base name.
  13166.                     if (array_item = g_script.FindOrAddVar(var_name, prefix_length + suffix_length, always_use))
  13167.                         array_item->Assign(subpat_pos);
  13168.                     suffix_length = sprintf(var_name_suffix, "Len%s", subpat_name[p]); // Append the subpattern name to the array's base name.
  13169.                     if (array_item = g_script.FindOrAddVar(var_name, prefix_length + suffix_length, always_use))
  13170.                         array_item->Assign(subpat_len);
  13171.                     // Fix for v1.0.45.01: Section below added.  See similar section further below for comments.
  13172.                     if (!subpat_not_matched && allow_dupe_subpat_names) // Explicitly check subpat_not_matched not pos/len so that behavior is consistent with the default mode (non-position).
  13173.                         for (n = p + 1; n < pattern_count; ++n) // Search to the right of this subpat to find others with the same name.
  13174.                             if (subpat_name[n] && !stricmp(subpat_name[n], subpat_name[p])) // Case-insensitive because unlike PCRE, named subpatterns conform to AHK convention of insensitive variable names.
  13175.                                 subpat_name[n] = ""; // Empty string signals subsequent iterations to skip it entirely.
  13176.                 }
  13177.                 //else an empty subpat name caused by "allow duplicate names".  Do nothing (see comments above).
  13178.             }
  13179.             else // This subpattern has no name, so write it out as its pattern number instead. For performance and memory utilization, it seems best to store only one or the other (named or number), not both.
  13180.             {
  13181.                 // For comments about this section, see the similar for-loop later below.
  13182.                 suffix_length = sprintf(var_name_suffix, "Pos%d", p); // Append the element number to the array's base name.
  13183.                 if (array_item = g_script.FindOrAddVar(var_name, prefix_length + suffix_length, always_use))
  13184.                     array_item->Assign(subpat_pos);
  13185.                 //else var couldn't be created: no error reporting currently, since it basically should never happen.
  13186.                 suffix_length = sprintf(var_name_suffix, "Len%d", p); // Append the element number to the array's base name.
  13187.                 if (array_item = g_script.FindOrAddVar(var_name, prefix_length + suffix_length, always_use))
  13188.                     array_item->Assign(subpat_len);
  13189.             }
  13190.         }
  13191.         goto free_and_return;
  13192.     } // if (get_positions_not_substrings)
  13193.  
  13194.     // Otherwise, we're in get-substring mode (not offset mode), so store the substring that matches each subpattern.
  13195.     for (; p < pattern_count; ++p, this_offset += 2) // Start at 1 because above already did pattern #0 (the full pattern).
  13196.     {
  13197.         // If both items in this_offset are -1, that means the substring wasn't populated because it's
  13198.         // subpattern wasn't needed to find a match (or there was no match for *anything*).  For example:
  13199.         // "(xyz)|(abc)" (in which only one is subpattern will match).
  13200.         // NOTE: PCRE isn't clear on this, but it seems likely that captured_pattern_count
  13201.         // (returned from pcre_exec()) can be less than pattern_count (from pcre_fullinfo/
  13202.         // PCRE_INFO_CAPTURECOUNT).  So the below takes this into account by not trusting values
  13203.         // in offset[] that are beyond captured_pattern_count.  Further evidence of this is PCRE's
  13204.         // pcre_copy_substring() function, which consults captured_pattern_count to decide whether to
  13205.         // consult the offset array. The formula below works even if captured_pattern_count==PCRE_ERROR_NOMATCH.
  13206.         subpat_not_matched = (p >= captured_pattern_count || this_offset[0] < 0); // Relies on short-circuit boolean order.
  13207.  
  13208.         if (subpat_name && subpat_name[p]) // This subpattern number has a name, so store it under that name.
  13209.         {
  13210.             if (*subpat_name[p]) // This check supports allow_dupe_subpat_names. See comments below.
  13211.             {
  13212.                 // This section is similar to the one in the "else" below, so see it for more comments.
  13213.                 strcpy(var_name_suffix, subpat_name[p]); // Append the subpat name to the array's base name.  strcpy() seems safe because PCRE almost certainly enforces the 32-char limit on subpattern names.
  13214.                 if (array_item = g_script.FindOrAddVar(var_name, 0, always_use))
  13215.                 {
  13216.                     if (subpat_not_matched)
  13217.                         array_item->Assign(); // Omit all parameters to make the var empty without freeing its memory (for performance, in case this RegEx is being used many times in a loop).
  13218.                     else
  13219.                     {
  13220.                         if (p < pattern_count-1 // i.e. there's at least one more subpattern after this one (if there weren't, making a copy of haystack wouldn't be necessary because overlap can't harm this final assignment).
  13221.                             && haystack == array_item->Contents()) // For more comments, see similar section higher above.
  13222.                             if (mem_to_free = _strdup(haystack))
  13223.                                 haystack = mem_to_free;
  13224.                         array_item->Assign(haystack + this_offset[0], this_offset[1] - this_offset[0]);
  13225.                         // Fix for v1.0.45.01: When the J option (allow duplicate named subpatterns) is in effect,
  13226.                         // PCRE returns entries for all the duplicates.  But we don't want an unmatched duplicate
  13227.                         // to overwrite a previously matched duplicate.  To prevent this, when we're here (i.e.
  13228.                         // this subpattern matched something), mark duplicate entries in the names array that lie
  13229.                         // to the right of this item to indicate that they should be skipped by subsequent iterations.
  13230.                         if (allow_dupe_subpat_names)
  13231.                             for (n = p + 1; n < pattern_count; ++n) // Search to the right of this subpat to find others with the same name.
  13232.                                 if (subpat_name[n] && !stricmp(subpat_name[n], subpat_name[p])) // Case-insensitive because unlike PCRE, named subpatterns conform to AHK convention of insensitive variable names.
  13233.                                     subpat_name[n] = ""; // Empty string signals subsequent iterations to skip it entirely.
  13234.                     }
  13235.                 }
  13236.                 //else var couldn't be created: no error reporting currently, since it basically should never happen.
  13237.             }
  13238.             //else an empty subpat name caused by "allow duplicate names".  Do nothing (see comments above).
  13239.         }
  13240.         else // This subpattern has no name, so instead write it out as its actual pattern number. For performance and memory utilization, it seems best to store only one or the other (named or number), not both.
  13241.         {
  13242.             _itoa(p, var_name_suffix, 10); // Append the element number to the array's base name.
  13243.             // To help performance (in case the linked list of variables is huge), tell it where
  13244.             // to start the search.  Use the base array name rather than the preceding element because,
  13245.             // for example, Array19 is alphabetially less than Array2, so we can't rely on the
  13246.             // numerical ordering:
  13247.             if (array_item = g_script.FindOrAddVar(var_name, 0, always_use))
  13248.             {
  13249.                 if (subpat_not_matched)
  13250.                     array_item->Assign(); // Omit all parameters to make the var empty without freeing its memory (for performance, in case this RegEx is being used many times in a loop).
  13251.                 else
  13252.                 {
  13253.                     if (p < pattern_count-1 // i.e. there's at least one more subpattern after this one (if there weren't, making a copy of haystack wouldn't be necessary because overlap can't harm this final assignment).
  13254.                         && haystack == array_item->Contents()) // For more comments, see similar section higher above.
  13255.                         if (mem_to_free = _strdup(haystack))
  13256.                             haystack = mem_to_free;
  13257.                     array_item->Assign(haystack + this_offset[0], this_offset[1] - this_offset[0]);
  13258.                 }
  13259.             }
  13260.             //else var couldn't be created: no error reporting currently, since it basically should never happen.
  13261.         }
  13262.     } // for() each subpattern.
  13263.  
  13264. free_and_return:
  13265.     if (mem_to_free)
  13266.         free(mem_to_free);
  13267. }
  13268.  
  13269.  
  13270.  
  13271. void BIF_Asc(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13272. {
  13273.     // Result will always be an integer (this simplifies scripts that work with binary zeros since an
  13274.     // empy string yields zero).
  13275.     // Caller has set aResultToken.symbol to a default of SYM_INTEGER, so no need to set it here.
  13276.     aResultToken.value_int64 = (UCHAR)*ExprTokenToString(*aParam[0], aResultToken.buf);
  13277. }
  13278.  
  13279.  
  13280.  
  13281. void BIF_Chr(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13282. {
  13283.     int param1 = (int)ExprTokenToInt64(*aParam[0]); // Convert to INT vs. UINT so that negatives can be detected.
  13284.     char *cp = aResultToken.buf; // If necessary, it will be moved to a persistent memory location by our caller.
  13285.     if (param1 < 0 || param1 > 255)
  13286.         *cp = '\0'; // Empty string indicates both Chr(0) and an out-of-bounds param1.
  13287.     else
  13288.     {
  13289.         cp[0] = param1;
  13290.         cp[1] = '\0';
  13291.     }
  13292.     aResultToken.symbol = SYM_STRING;
  13293.     aResultToken.marker = cp;
  13294. }
  13295.  
  13296.  
  13297.  
  13298. void BIF_NumGet(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13299. {
  13300.     size_t right_side_bound, target; // Don't make target a pointer-type because the integer offset might not be a multiple of 4 (i.e. the below increments "target" directly by "offset" and we don't want that to use pointer math).
  13301.     ExprTokenType &target_token = *aParam[0];
  13302.     if (target_token.symbol == SYM_VAR) // SYM_VAR's Type() is always VAR_NORMAL.
  13303.     {
  13304.         target = (size_t)target_token.var->Contents();
  13305.         right_side_bound = target + target_token.var->Capacity(); // This is first illegal address to the right of target.
  13306.     }
  13307.     else
  13308.         target = (size_t)ExprTokenToInt64(target_token);
  13309.  
  13310.     if (aParamCount > 1) // Parameter "offset" is present, so increment the address by that amount.  For flexibility, this is done even when the target isn't a variable.
  13311.         target += (int)ExprTokenToInt64(*aParam[1]); // Cast to int vs. size_t to support negative offsets.
  13312.  
  13313.     BOOL is_signed;
  13314.     size_t size = 4; // Set default.
  13315.  
  13316.     if (aParamCount < 3) // The "type" parameter is absent (which is most often the case), so use defaults.
  13317.         is_signed = FALSE;
  13318.         // And keep "size" at its default set earlier.
  13319.     else // An explicit "type" is present.
  13320.     {
  13321.         char *type = ExprTokenToString(*aParam[2], aResultToken.buf);
  13322.         if (toupper(*type) == 'U') // Unsigned.
  13323.         {
  13324.             ++type; // Remove the first character from further consideration.
  13325.             is_signed = FALSE;
  13326.         }
  13327.         else
  13328.             is_signed = TRUE;
  13329.  
  13330.         switch(toupper(*type)) // Override "size" and aResultToken.symbol if type warrants it. Note that the above has omitted the leading "U", if present, leaving type as "Int" vs. "Uint", etc.
  13331.         {
  13332.         case 'I':
  13333.             if (strchr(type, '6')) // Int64. It's checked this way for performance, and to avoid access violation if string is bogus and too short such as "i64".
  13334.                 size = 8;
  13335.             //else keep "size" at its default set earlier.
  13336.             break;
  13337.         case 'S': size = 2; break; // Short.
  13338.         case 'C': size = 1; break; // Char.
  13339.  
  13340.         case 'D': size = 8; // Double.  NO "BREAK": fall through to below.
  13341.         case 'F': // OR FELL THROUGH FROM ABOVE.
  13342.             aResultToken.symbol = SYM_FLOAT; // Override the default of SYM_INTEGER set by our caller.
  13343.             // In the case of 'F', leave "size" at its default set earlier.
  13344.             break;
  13345.         // default: For any unrecognized values, keep "size" and aResultToken.symbol at their defaults set earlier
  13346.         // (for simplicity).
  13347.         }
  13348.     }
  13349.  
  13350.     // If the target is a variable, the following check ensures that the memory to be read lies within its capacity.
  13351.     // This seems superior to an exception handler because exception handlers only catch illegal addresses,
  13352.     // not ones that are technically legal but unintentionally bugs due to being beyond a variable's capacity.
  13353.     // Moreover, __try/except is larger in code size. Another possible alternative is IsBadReadPtr()/IsBadWritePtr(),
  13354.     // but those are discouraged by MSDN.
  13355.     // The following aren't covered by the check below:
  13356.     // - Due to rarity of negative offsets, only the right-side boundary is checked, not the left.
  13357.     // - Due to rarity and to simplify things, Float/Double (which "return" higher above) aren't checked.
  13358.     if (target < 1024 // Basic sanity check to catch incoming raw addresses that are zero or blank.
  13359.         || target_token.symbol == SYM_VAR && target+size > right_side_bound) // i.e. it's ok if target+size==right_side_bound because the last byte to be read is actually at target+size-1. In other words, the position of the last possible terminator within the variable's capacity is considered an allowable address.
  13360.     {
  13361.         aResultToken.symbol = SYM_STRING;
  13362.         aResultToken.marker = "";
  13363.         return;
  13364.     }
  13365.  
  13366.     switch(size)
  13367.     {
  13368.     case 4: // Listed first for performance.
  13369.         if (aResultToken.symbol == SYM_FLOAT)
  13370.             aResultToken.value_double = *(float *)target;
  13371.         else if (is_signed)
  13372.             aResultToken.value_int64 = *(int *)target; // aResultToken.symbol was set to SYM_FLOAT or SYM_INTEGER higher above.
  13373.         else
  13374.             aResultToken.value_int64 = *(unsigned int *)target;
  13375.         break;
  13376.     case 8:
  13377.         // The below correctly copies both DOUBLE and INT64 into the union.
  13378.         // Unsigned 64-bit integers aren't supported because variables/expressions can't support them.
  13379.         aResultToken.value_int64 = *(__int64 *)target;
  13380.         break;
  13381.     case 2:
  13382.         if (is_signed) // Don't use ternary because that messes up type-casting.
  13383.             aResultToken.value_int64 = *(short *)target;
  13384.         else
  13385.             aResultToken.value_int64 = *(unsigned short *)target;
  13386.         break;
  13387.     default: // size 1
  13388.         if (is_signed) // Don't use ternary because that messes up type-casting.
  13389.             aResultToken.value_int64 = *(char *)target;
  13390.         else
  13391.             aResultToken.value_int64 = *(unsigned char *)target;
  13392.     }
  13393. }
  13394.  
  13395.  
  13396.  
  13397. void BIF_NumPut(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13398. {
  13399.     // Load-time validation has ensured that at least the first two parameters are present.
  13400.     ExprTokenType &token_to_write = *aParam[0];
  13401.  
  13402.     size_t right_side_bound, target; // Don't make target a pointer-type because the integer offset might not be a multiple of 4 (i.e. the below increments "target" directly by "offset" and we don't want that to use pointer math).
  13403.     ExprTokenType &target_token = *aParam[1];
  13404.     if (target_token.symbol == SYM_VAR) // SYM_VAR's Type() is always VAR_NORMAL.
  13405.     {
  13406.         target = (size_t)target_token.var->Contents();
  13407.         right_side_bound = target + target_token.var->Capacity(); // This is first illegal address to the right of target.
  13408.     }
  13409.     else
  13410.         target = (size_t)ExprTokenToInt64(target_token);
  13411.  
  13412.     if (aParamCount > 2) // Parameter "offset" is present, so increment the address by that amount.  For flexibility, this is done even when the target isn't a variable.
  13413.         target += (int)ExprTokenToInt64(*aParam[2]); // Cast to int vs. size_t to support negative offsets.
  13414.  
  13415.     size_t size = 4;        // Set defaults.
  13416.     BOOL is_integer = TRUE; //
  13417.  
  13418.     if (aParamCount > 3) // The "type" parameter is present (which is somewhat unusual).
  13419.     {
  13420.         char *type = ExprTokenToString(*aParam[3], aResultToken.buf);
  13421.         if (toupper(*type) == 'U') // Unsigned; but in the case of NumPut, it doesn't matter so ignore it.
  13422.             ++type; // Remove the first character from further consideration.
  13423.  
  13424.         switch(toupper(*type)) // Override "size" and is_integer if type warrants it. Note that the above has omitted the leading "U", if present, leaving type as "Int" vs. "Uint", etc.
  13425.         {
  13426.         case 'I':
  13427.             if (strchr(type, '6')) // Int64. It's checked this way for performance, and to avoid access violation if string is bogus and too short such as "i64".
  13428.                 size = 8;
  13429.             //else keep "size" at its default set earlier.
  13430.             break;
  13431.         case 'S': size = 2; break; // Short.
  13432.         case 'C': size = 1; break; // Char.
  13433.  
  13434.         case 'D': size = 8; // Double.  NO "BREAK": fall through to below.
  13435.         case 'F': // OR FELL THROUGH FROM ABOVE.
  13436.             is_integer = FALSE; // Override the default set earlier.
  13437.             // In the case of 'F', leave "size" at its default set earlier.
  13438.             break;
  13439.         // default: For any unrecognized values, keep "size" and is_integer at their defaults set earlier
  13440.         // (for simplicity).
  13441.         }
  13442.     }
  13443.  
  13444.     aResultToken.value_int64 = target + size; // This is used below and also as NumPut's return value. It's the address to the right of the item to be written.  aResultToken.symbol was set to SYM_INTEGER by our caller.
  13445.  
  13446.     // See comments in NumGet about the following section:
  13447.     if (target < 1024 // Basic sanity check to catch incoming raw addresses that are zero or blank.
  13448.         || target_token.symbol == SYM_VAR && aResultToken.value_int64 > right_side_bound) // i.e. it's ok if target+size==right_side_bound because the last byte to be read is actually at target+size-1. In other words, the position of the last possible terminator within the variable's capacity is considered an allowable address.
  13449.     {
  13450.         aResultToken.symbol = SYM_STRING;
  13451.         aResultToken.marker = "";
  13452.         return;
  13453.     }
  13454.  
  13455.     __int64 int64_to_write;
  13456.     if (is_integer)
  13457.         int64_to_write = ExprTokenToInt64(token_to_write);
  13458.  
  13459.     switch(size)
  13460.     {
  13461.     case 4: // Listed first for performance.
  13462.         if (is_integer)
  13463.             *(unsigned int *)target = (unsigned int)int64_to_write;
  13464.         else // Float (32-bit).
  13465.             *(float *)target = (float)ExprTokenToDouble(token_to_write);
  13466.         break;
  13467.     case 8:
  13468.         if (is_integer)
  13469.             *(__int64 *)target = int64_to_write; // Unsigned 64-bit not supported because variables/expressions can't support them.
  13470.         else // Double (64-bit).
  13471.             *(double *)target = ExprTokenToDouble(token_to_write);
  13472.         break;
  13473.     case 2:
  13474.         *(unsigned short *)target = (unsigned short)int64_to_write;
  13475.         break;
  13476.     default: // size 1
  13477.         *(unsigned char *)target = (unsigned char)int64_to_write;
  13478.     }
  13479. }
  13480.  
  13481.  
  13482.  
  13483. void BIF_IsLabel(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13484. // For performance and code-size reasons, this function does not currently return what
  13485. // type of label it is (hotstring, hotkey, or generic).  To preserve the option to do
  13486. // this in the future, it has been documented that the function returns non-zero rather
  13487. // than "true".  However, if performance is an issue (since scripts that use IsLabel are
  13488. // often performance sensitive), it might be better to add a second parameter that tells
  13489. // IsLabel to look up the type of label, and return it as a number or letter.
  13490. {
  13491.     aResultToken.value_int64 = g_script.FindLabel(ExprTokenToString(*aParam[0], aResultToken.buf)) ? 1 : 0;
  13492. }
  13493.  
  13494.  
  13495.  
  13496. void BIF_GetKeyState(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13497. {
  13498.     char key_name_buf[MAX_FORMATTED_NUMBER_LENGTH + 1]; // Because aResultToken.buf is used for something else below.
  13499.     char *key_name = ExprTokenToString(*aParam[0], key_name_buf);
  13500.     // Keep this in sync with GetKeyJoyState().
  13501.     // See GetKeyJoyState() for more comments about the following lines.
  13502.     JoyControls joy;
  13503.     int joystick_id;
  13504.     vk_type vk = TextToVK(key_name);
  13505.     if (!vk)
  13506.     {
  13507.         aResultToken.symbol = SYM_STRING; // ScriptGetJoyState() also requires that this be initialized.
  13508.         if (   !(joy = (JoyControls)ConvertJoy(key_name, &joystick_id))   )
  13509.             aResultToken.marker = "";
  13510.         else
  13511.         {
  13512.             // The following must be set for ScriptGetJoyState():
  13513.             aResultToken.marker = aResultToken.buf; // If necessary, it will be moved to a persistent memory location by our caller.
  13514.             ScriptGetJoyState(joy, joystick_id, aResultToken, true);
  13515.         }
  13516.         return;
  13517.     }
  13518.     // Since above didn't return: There is a virtual key (not a joystick control).
  13519.     char mode_buf[MAX_FORMATTED_NUMBER_LENGTH + 1];
  13520.     char *mode = (aParamCount > 1) ? ExprTokenToString(*aParam[1], mode_buf) : "";
  13521.     KeyStateTypes key_state_type;
  13522.     switch (toupper(*mode)) // Second parameter.
  13523.     {
  13524.     case 'T': key_state_type = KEYSTATE_TOGGLE; break; // Whether a toggleable key such as CapsLock is currently turned on.
  13525.     case 'P': key_state_type = KEYSTATE_PHYSICAL; break; // Physical state of key.
  13526.     default: key_state_type = KEYSTATE_LOGICAL;
  13527.     }
  13528.     // Caller has set aResultToken.symbol to a default of SYM_INTEGER, so no need to set it here.
  13529.     aResultToken.value_int64 = ScriptGetKeyState(vk, key_state_type); // 1 for down and 0 for up.
  13530. }
  13531.  
  13532.  
  13533.  
  13534. void BIF_VarSetCapacity(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13535. // Returns: The variable's new capacity.
  13536. // Parameters:
  13537. // 1: Target variable (unquoted).
  13538. // 2: Requested capacity.
  13539. // 3: Byte-value to fill the variable with (e.g. 0 to have the same effect as ZeroMemory).
  13540. {
  13541.     // Caller has set aResultToken.symbol to a default of SYM_INTEGER, so no need to set it here.
  13542.     aResultToken.value_int64 = 0; // Set default. In spite of being ambiguous with the result of Free(), 0 seems a little better than -1 since it indicates "no capacity" and is also equal to "false" for easy use in expressions.
  13543.     if (aParam[0]->symbol == SYM_VAR) // SYM_VAR's Type() is always VAR_NORMAL.
  13544.     {
  13545.         Var &var = *aParam[0]->var; // For performance and convenience. Note: SYM_VAR's Type() is always VAR_NORMAL.
  13546.         if (aParamCount > 1) // Second parameter is present.
  13547.         {
  13548.             VarSizeType new_capacity = (VarSizeType)ExprTokenToInt64(*aParam[1]);
  13549.             if (new_capacity == -1) // Adjust variable's internal length. Since new_capacity is unsigned, compare directly to -1 rather than doing <0.
  13550.             {
  13551.                 // Seems more useful to report length vs. capacity in this special case. Scripts might be able
  13552.                 // to use this to boost performance.
  13553.                 aResultToken.value_int64 = var.Length() = (VarSizeType)strlen(var.Contents());
  13554.                 var.Close(); // v1.0.44.14: Removes the VAR_ATTRIB_BINARY_CLIP (if it was present) because it seems more flexible to convert binary-to-normal rather than checking IsBinaryClip() then doing nothing if it binary.
  13555.                 return;
  13556.             }
  13557.             // Since above didn't return:
  13558.             if (new_capacity)
  13559.             {
  13560.                 var.Assign(NULL, new_capacity, true, false); // This also destroys the variables contents.
  13561.                 VarSizeType capacity;
  13562.                 if (aParamCount > 2 && (capacity = var.Capacity()) > 1) // Third parameter is present and var has enough capacity to make FillMemory() meaningful.
  13563.                 {
  13564.                     --capacity; // Convert to script-POV capacity. To avoid underflow, do this only now that Capacity() is known not to be zero.
  13565.                     // The following uses capacity-1 because the last byte of a variable should always
  13566.                     // be left as a binary zero to avoid crashes and problems due to unterminated strings.
  13567.                     // In other words, a variable's usable capacity from the script's POV is always one
  13568.                     // less than its actual capacity:
  13569.                     BYTE fill_byte = (BYTE)ExprTokenToInt64(*aParam[2]); // For simplicity, only numeric characters are supported, not something like "a" to mean the character 'a'.
  13570.                     char *contents = var.Contents();
  13571.                     FillMemory(contents, capacity, fill_byte); // Last byte of variable is always left as a binary zero.
  13572.                     contents[capacity] = '\0'; // Must terminate because nothing else is explicitly reponsible for doing it.
  13573.                     var.Length() = fill_byte ? capacity : 0; // Length is same as capacity unless fill_byte is zero.
  13574.                 }
  13575.                 else
  13576.                     // By design, Assign() has already set the length of the variable to reflect new_capacity.
  13577.                     // This is not what is wanted in this case since it should be truly empty.
  13578.                     var.Length() = 0;
  13579.             }
  13580.             else // ALLOC_SIMPLE, due to its nature, will not actually be freed, which is documented.
  13581.                 var.Free();
  13582.         } // if (aParamCount > 1)
  13583.         //else the var is not altered; instead, the current capacity is reported, which seems more intuitive/useful than having it do a Free().
  13584.         if (aResultToken.value_int64 = var.Capacity()) // Don't subtract 1 here in lieu doing it below (avoids underflow).
  13585.             --aResultToken.value_int64; // Omit the room for the zero terminator since script capacity is defined as length vs. size.
  13586.     } // (aParam[0]->symbol == SYM_VAR)
  13587. }
  13588.  
  13589.  
  13590.  
  13591. void BIF_FileExist(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13592. {
  13593.     char filename_buf[MAX_FORMATTED_NUMBER_LENGTH + 1]; // Because aResultToken.buf is used for something else below.
  13594.     char *filename = ExprTokenToString(*aParam[0], filename_buf);
  13595.     aResultToken.marker = aResultToken.buf; // If necessary, it will be moved to a persistent memory location by our caller.
  13596.     aResultToken.symbol = SYM_STRING;
  13597.     DWORD attr;
  13598.     if (DoesFilePatternExist(filename, &attr))
  13599.     {
  13600.         // Yield the attributes of the first matching file.  If not match, yield an empty string.
  13601.         // This relies upon the fact that a file's attributes are never legitimately zero, which
  13602.         // seems true but in case it ever isn't, this forces a non-empty string be used.
  13603.         // UPDATE for v1.0.44.03: Someone reported that an existing file (created by NTbackup.exe) can
  13604.         // apparently have undefined/invalid attributes (i.e. attributes that have no matching letter in
  13605.         // "RASHNDOCT").  Although this is unconfirmed, it's easy to handle that possibility here by
  13606.         // checking for a blank string.  This allows FileExist() to report boolean TRUE rather than FALSE
  13607.         // for such "mystery files":
  13608.         FileAttribToStr(aResultToken.marker, attr);
  13609.         if (!*aResultToken.marker) // See above.
  13610.         {
  13611.             // The attributes might be all 0, but more likely the file has some of the newer attributes
  13612.             // such as FILE_ATTRIBUTE_ENCRYPTED (or has undefined attributs).  So rather than storing attr as
  13613.             // a hex number (which could be zero and thus defeat FileExist's ability to detect the file), it
  13614.             // seems better to store some arbirary letter (other than those in "RASHNDOCT") so that FileExist's
  13615.             // return value is seen as boolean "true".
  13616.             aResultToken.marker[0] = 'X';
  13617.             aResultToken.marker[1] = '\0';
  13618.         }
  13619.     }
  13620.     else // Empty string is the indicator of "not found" (seems more consistent than using an integer 0, since caller might rely on it being SYM_STRING).
  13621.         *aResultToken.marker = '\0';
  13622. }
  13623.  
  13624.  
  13625.  
  13626. void BIF_WinExistActive(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13627. {
  13628.     char *bif_name = aResultToken.marker;  // Save this early for maintainability (it is the name of the function, provided by the caller).
  13629.     aResultToken.symbol = SYM_STRING; // Returns a string to preserve hex format.
  13630.  
  13631.     char *param[4], param_buf[4][MAX_FORMATTED_NUMBER_LENGTH + 1];
  13632.     for (int j = 0; j < 4; ++j) // For each formal parameter, including optional ones.
  13633.         param[j] = (j >= aParamCount) ? "" : ExprTokenToString(*aParam[j], param_buf[j]);
  13634.         // For above, the following are notes from a time when this function was part of expression evaluation:
  13635.         // Assign empty string if no actual to go with it.
  13636.         // Otherwise, assign actual parameter's value to the formal parameter.
  13637.         // The stack can contain both generic and specific operands.  Specific operands were
  13638.         // evaluated by a previous iteration of this section.  Generic ones were pushed as-is
  13639.         // onto the stack by a previous iteration.
  13640.  
  13641.     // Should be called the same was as ACT_IFWINEXIST and ACT_IFWINACTIVE:
  13642.     HWND found_hwnd = (toupper(bif_name[3]) == 'E') // Win[E]xist.
  13643.         ? WinExist(g, param[0], param[1], param[2], param[3], false, true)
  13644.         : WinActive(g, param[0], param[1], param[2], param[3], true);
  13645.     aResultToken.marker = aResultToken.buf;
  13646.     aResultToken.marker[0] = '0';
  13647.     aResultToken.marker[1] = 'x';
  13648.     _ui64toa((unsigned __int64)found_hwnd, aResultToken.marker + 2, 16); // If necessary, it will be moved to a persistent memory location by our caller.
  13649. }
  13650.  
  13651.  
  13652.  
  13653. void BIF_Round(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13654. // For simplicity and backward compatibility, this always yields something numeric (or a string that's numeric).
  13655. // Even Round(empty_or_unintialized_var) is zero rather than "".
  13656. {
  13657.     char *buf = aResultToken.buf; // Must be saved early since below overwrites the union (better maintainability too).
  13658.  
  13659.     // See TRANS_CMD_ROUND for details.
  13660.     int param2;
  13661.     double multiplier;
  13662.     if (aParamCount > 1)
  13663.     {
  13664.         param2 = (int)ExprTokenToInt64(*aParam[1]);
  13665.         multiplier = qmathPow(10, param2);
  13666.     }
  13667.     else // Omitting the parameter is the same as explicitly specifying 0 for it.
  13668.     {
  13669.         param2 = 0;
  13670.         multiplier = 1;
  13671.     }
  13672.     double value = ExprTokenToDouble(*aParam[0]);
  13673.     aResultToken.value_double = (value >= 0.0 ? qmathFloor(value * multiplier + 0.5)
  13674.         : qmathCeil(value * multiplier - 0.5)) / multiplier;
  13675.  
  13676.     // If incoming value is an integer, it seems best for flexibility to convert it to a
  13677.     // floating point number whenever the second param is >0.  That way, it can be used
  13678.     // to "cast" integers into floats.  Conversely, it seems best to yield an integer
  13679.     // whenever the second param is <=0 or omitted.
  13680.     if (param2 > 0) // aResultToken.value_double already contains the result.
  13681.     {
  13682.         // v1.0.44.01: Since Round (in its param2>0 mode) is almost always used to facilitate some kind of
  13683.         // display or output of the number (hardly ever for intentional reducing the precision of a floating
  13684.         // point math operation), it seems best by default to omit only those trailing zeroes that are beyond
  13685.         // the specified number of decimal places.  This is done by converting the result into a string here,
  13686.         // which will cause the expression evaluation to write out the final result as this very string as long
  13687.         // as no further floating point math is done on it (such as Round(3.3333, 2)+0).  Also note that not
  13688.         // all trailing zeros are removed because it is often the intent that exactly the number of decimal
  13689.         // places specified should be *shown* (for column alignment, etc.).  For example, Round(3.5, 2) should
  13690.         // be 3.50 not 3.5.  Similarly, Round(1, 2) should be 1.00 not 1 (see above comment about "casting" for
  13691.         // why.
  13692.         // Performance: This method is about twice as slow as the old method (which did merely the line
  13693.         // "aResultToken.symbol = SYM_FLOAT" in place of the below).  However, that might be something
  13694.         // that can be further optimized in the caller (its calls to strlen, memcpy, etc. might be optimized
  13695.         // someday to omit certain calls when very simply situations allow it).  In addition, twice as slow is
  13696.         // not going to impact the vast majority of scripts since as mentioned above, Round (in its param2>0
  13697.         // mode) is almost always used for displaying data, not for intensive operations within a expressions.
  13698.         // AS DOCUMENTED: Round(..., positive_number) doesn't obey SetFormat (nor scientific notation).
  13699.         // The script can force Round(x, 2) to obey SetFormat by adding 0 to the result (if it wants).
  13700.         // Also, a new parameter an be added someday to trim excess trailing zeros from param2>0's result
  13701.         // (e.g. Round(3.50, 2, true) can be 3.5 rather than 3.50), but this seems less often desired due to
  13702.         // column alignment and other goals where consistency is important.
  13703.         sprintf(buf, "%0.*f", param2, aResultToken.value_double); // %f can handle doubles in MSVC++.
  13704.         aResultToken.marker = buf;
  13705.         aResultToken.symbol = SYM_STRING;
  13706.     }
  13707.     else
  13708.         // Fix for v1.0.47.04: See BIF_FloorCeil() for explanation of this fix.  Currently, the only known example
  13709.         // of when the fix is necessary is the following script in release mode (not debug mode):
  13710.         //   myNumber  := 1043.22  ; Bug also happens with -1043.22 (negative).
  13711.         //   myRounded1 := Round( myNumber, -1 )  ; Stores 1040 (correct).
  13712.         //   ChartModule := DllCall("LoadLibrary", "str", "rmchart.dll")
  13713.         //   myRounded2 := Round( myNumber, -1 )  ; Stores 1039 (wrong).
  13714.         aResultToken.value_int64 = (__int64)(aResultToken.value_double + (aResultToken.value_double > 0 ? 0.2 : -0.2));
  13715.         // Formerly above was: aResultToken.value_int64 = (__int64)aResultToken.value_double;
  13716.         // Caller has set aResultToken.symbol to a default of SYM_INTEGER, so no need to set it here.
  13717. }
  13718.  
  13719.  
  13720.  
  13721. void BIF_FloorCeil(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13722. // Probably saves little code size to merge extremely short/fast functions, hence FloorCeil.
  13723. // Floor() rounds down to the nearest integer; that is, to the integer that lies to the left on the
  13724. // number line (this is not the same as truncation because Floor(-1.2) is -2, not -1).
  13725. // Ceil() rounds up to the nearest integer; that is, to the integer that lies to the right on the number line.
  13726. //
  13727. // For simplicity and backward compatibility, a numeric result is always returned (even if the input
  13728. // is non-numeric or an empty string).
  13729. {
  13730.     // The code here is similar to that in TRANS_CMD_FLOOR/CEIL, so maintain them together.
  13731.     // The qmath routines are used because Floor() and Ceil() are deceptively difficult to implement in a way
  13732.     // that gives the correct result in all permutations of the following:
  13733.     // 1) Negative vs. positive input.
  13734.     // 2) Whether or not the input is already an integer.
  13735.     // Therefore, do not change this without conduction a thorough test.
  13736.     double x = ExprTokenToDouble(*aParam[0]);
  13737.     x = (toupper(aResultToken.marker[0]) == 'F') ? qmathFloor(x) : qmathCeil(x);
  13738.     // Fix for v1.0.40.05: For some inputs, qmathCeil/Floor yield a number slightly to the left of the target
  13739.     // integer, while for others they yield one slightly to the right.  For example, Ceil(62/61) and Floor(-4/3)
  13740.     // yield a double that would give an incorrect answer if it were simply truncated to an integer via
  13741.     // type casting.  The below seems to fix this without breaking the answers for other inputs (which is
  13742.     // surprisingly harder than it seemed).  There is a similar fix in BIF_Round().
  13743.     aResultToken.value_int64 = (__int64)(x + (x > 0 ? 0.2 : -0.2));
  13744.     // Caller has set aResultToken.symbol to a default of SYM_INTEGER, so no need to set it here.
  13745. }
  13746.  
  13747.  
  13748.  
  13749. void BIF_Mod(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13750. {
  13751.     // Load-time validation has already ensured there are exactly two parameters.
  13752.     // "Cast" each operand to Int64/Double depending on whether it has a decimal point.
  13753.     if (!ExprTokenToDoubleOrInt(*aParam[0]) || !ExprTokenToDoubleOrInt(*aParam[1])) // Non-operand or non-numeric string.
  13754.     {
  13755.         aResultToken.symbol = SYM_STRING;
  13756.         aResultToken.marker = "";
  13757.         return;
  13758.     }
  13759.     if (aParam[0]->symbol == SYM_INTEGER && aParam[1]->symbol == SYM_INTEGER)
  13760.     {
  13761.         if (!aParam[1]->value_int64) // Divide by zero.
  13762.         {
  13763.             aResultToken.symbol = SYM_STRING;
  13764.             aResultToken.marker = "";
  13765.         }
  13766.         else
  13767.             // For performance, % is used vs. qmath for integers.
  13768.             // Caller has set aResultToken.symbol to a default of SYM_INTEGER, so no need to set it here.
  13769.             aResultToken.value_int64 = aParam[0]->value_int64 % aParam[1]->value_int64;
  13770.     }
  13771.     else // At least one is a floating point number.
  13772.     {
  13773.         double dividend = ExprTokenToDouble(*aParam[0]);
  13774.         double divisor = ExprTokenToDouble(*aParam[1]);
  13775.         if (divisor == 0.0) // Divide by zero.
  13776.         {
  13777.             aResultToken.symbol = SYM_STRING;
  13778.             aResultToken.marker = "";
  13779.         }
  13780.         else
  13781.         {
  13782.             aResultToken.symbol = SYM_FLOAT;
  13783.             aResultToken.value_double = qmathFmod(dividend, divisor);
  13784.         }
  13785.     }
  13786. }
  13787.  
  13788.  
  13789.  
  13790. void BIF_Abs(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13791. {
  13792.     // Unlike TRANS_CMD_ABS, which removes the minus sign from the string if it has one,
  13793.     // this is done in a more traditional way.  It's hard to imagine needing the minus
  13794.     // sign removal method here since a negative hex literal such as -0xFF seems too rare
  13795.     // to worry about.  One additional reason not to remove minus signs from strings is
  13796.     // that it might produce inconsistent results depending on whether the operand is
  13797.     // generic (SYM_OPERAND) and numeric.  In other words, abs() shouldn't treat a
  13798.     // sub-expression differently than a numeric literal.
  13799.     aResultToken = *aParam[0]; // Structure/union copy.
  13800.     // v1.0.40.06: ExprTokenToDoubleOrInt() and here has been fixed to set proper result to be empty string
  13801.     // when the incoming parameter is non-numeric.
  13802.     if (!ExprTokenToDoubleOrInt(aResultToken)) // "Cast" token to Int64/Double depending on whether it has a decimal point.
  13803.         // Non-operand or non-numeric string. ExprTokenToDoubleOrInt() has already set the token to be an
  13804.         // empty string for us.
  13805.         return;
  13806.     if (aResultToken.symbol == SYM_INTEGER)
  13807.     {
  13808.         // The following method is used instead of __abs64() to allow linking against the multi-threaded
  13809.         // DLLs (vs. libs) if that option is ever used (such as for a minimum size AutoHotkeySC.bin file).
  13810.         // It might be somewhat faster than __abs64() anyway, unless __abs64() is a macro or inline or something.
  13811.         if (aResultToken.value_int64 < 0)
  13812.             aResultToken.value_int64 = -aResultToken.value_int64;
  13813.     }
  13814.     else // Must be SYM_FLOAT due to the conversion above.
  13815.         aResultToken.value_double = qmathFabs(aResultToken.value_double);
  13816. }
  13817.  
  13818.  
  13819.  
  13820. void BIF_Sin(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13821. // For simplicity and backward compatibility, a numeric result is always returned (even if the input
  13822. // is non-numeric or an empty string).
  13823. {
  13824.     aResultToken.symbol = SYM_FLOAT;
  13825.     aResultToken.value_double = qmathSin(ExprTokenToDouble(*aParam[0]));
  13826. }
  13827.  
  13828.  
  13829.  
  13830. void BIF_Cos(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13831. // For simplicity and backward compatibility, a numeric result is always returned (even if the input
  13832. // is non-numeric or an empty string).
  13833. {
  13834.     aResultToken.symbol = SYM_FLOAT;
  13835.     aResultToken.value_double = qmathCos(ExprTokenToDouble(*aParam[0]));
  13836. }
  13837.  
  13838.  
  13839.  
  13840. void BIF_Tan(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13841. // For simplicity and backward compatibility, a numeric result is always returned (even if the input
  13842. // is non-numeric or an empty string).
  13843. {
  13844.     aResultToken.symbol = SYM_FLOAT;
  13845.     aResultToken.value_double = qmathTan(ExprTokenToDouble(*aParam[0]));
  13846. }
  13847.  
  13848.  
  13849.  
  13850. void BIF_ASinACos(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13851. {
  13852.     double value = ExprTokenToDouble(*aParam[0]);
  13853.     if (value > 1 || value < -1) // ASin and ACos aren't defined for such values.
  13854.     {
  13855.         aResultToken.symbol = SYM_STRING;
  13856.         aResultToken.marker = "";
  13857.     }
  13858.     else
  13859.     {
  13860.         // For simplicity and backward compatibility, a numeric result is always returned in this case (even if
  13861.         // the input is non-numeric or an empty string).
  13862.         aResultToken.symbol = SYM_FLOAT;
  13863.         // Below: marker contains either "ASin" or "ACos"
  13864.         aResultToken.value_double = (toupper(aResultToken.marker[1]) == 'S') ? qmathAsin(value) : qmathAcos(value);
  13865.     }
  13866. }
  13867.  
  13868.  
  13869.  
  13870. void BIF_ATan(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13871. // For simplicity and backward compatibility, a numeric result is always returned (even if the input
  13872. // is non-numeric or an empty string).
  13873. {
  13874.     aResultToken.symbol = SYM_FLOAT;
  13875.     aResultToken.value_double = qmathAtan(ExprTokenToDouble(*aParam[0]));
  13876. }
  13877.  
  13878.  
  13879.  
  13880. void BIF_Exp(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13881. // For simplicity and backward compatibility, a numeric result is always returned (even if the input
  13882. // is non-numeric or an empty string).
  13883. {
  13884.     aResultToken.symbol = SYM_FLOAT;
  13885.     aResultToken.value_double = qmathExp(ExprTokenToDouble(*aParam[0]));
  13886. }
  13887.  
  13888.  
  13889.  
  13890. void BIF_SqrtLogLn(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13891. {
  13892.     double value = ExprTokenToDouble(*aParam[0]);
  13893.     if (value < 0) // Result is undefined in these cases, so make blank to indicate.
  13894.     {
  13895.         aResultToken.symbol = SYM_STRING;
  13896.         aResultToken.marker = "";
  13897.     }
  13898.     else
  13899.     {
  13900.         // For simplicity and backward compatibility, a numeric result is always returned in this case (even if
  13901.         // the input is non-numeric or an empty string).
  13902.         aResultToken.symbol = SYM_FLOAT;
  13903.         switch (toupper(aResultToken.marker[1]))
  13904.         {
  13905.         case 'Q': // S[q]rt
  13906.             aResultToken.value_double = qmathSqrt(value);
  13907.             break;
  13908.         case 'O': // L[o]g
  13909.             aResultToken.value_double = qmathLog10(value);
  13910.             break;
  13911.         default: // L[n]
  13912.             aResultToken.value_double = qmathLog(value);
  13913.         }
  13914.     }
  13915. }
  13916.  
  13917.  
  13918.  
  13919. void BIF_OnMessage(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  13920. // Returns: An empty string on failure or the name of a function (depends on mode) on success.
  13921. // Parameters:
  13922. // 1: Message number to monitor.
  13923. // 2: Name of the function that will monitor the message.
  13924. // 3: (FUTURE): A flex-list of space-delimited option words/letters.
  13925. {
  13926.     char *buf = aResultToken.buf; // Must be saved early since below overwrites the union (better maintainability too).
  13927.     // Set default result in case of early return; a blank value:
  13928.     aResultToken.symbol = SYM_STRING;
  13929.     aResultToken.marker = "";
  13930.  
  13931.     // Load-time validation has ensured there's at least one parameter for use here:
  13932.     UINT specified_msg = (UINT)ExprTokenToInt64(*aParam[0]); // Parameter #1
  13933.  
  13934.     Func *func = NULL;           // Set defaults.
  13935.     bool mode_is_delete = false; //
  13936.     if (aParamCount > 1) // Parameter #2 is present.
  13937.     {
  13938.         char *func_name = ExprTokenToString(*aParam[1], buf); // Resolve parameter #2.
  13939.         if (*func_name)
  13940.         {
  13941.             if (   !(func = g_script.FindFunc(func_name))   ) // Nonexistent function.
  13942.                 return; // Yield the default return value set earlier.
  13943.             // If too many formal parameters or any are ByRef/optional, indicate failure.
  13944.             // This helps catch bugs in scripts that are assigning the wrong function to a monitor.
  13945.             // It also preserves additional parameters for possible future use (i.e. avoids breaking
  13946.             // existing scripts if more formal parameters are supported in a future version).
  13947.             if (func->mIsBuiltIn || func->mParamCount > 4 || func->mMinParams < func->mParamCount) // Too many params, or some are optional.
  13948.                 return; // Yield the default return value set earlier.
  13949.             for (int i = 0; i < func->mParamCount; ++i) // Check if any formal parameters are ByRef.
  13950.                 if (func->mParam[i].is_byref)
  13951.                     return; // Yield the default return value set earlier.
  13952.         }
  13953.         else // Explicitly blank function name ("") means delete this item.  By contrast, an omitted second parameter means "give me current function of this message".
  13954.             mode_is_delete = true;
  13955.     }
  13956.  
  13957.     // If this is the first use of the g_MsgMonitor array, create it now rather than later to reduce code size
  13958.     // and help the maintainability of sections further below. The following relies on short-circuit boolean order:
  13959.     if (!g_MsgMonitor && !(g_MsgMonitor = (MsgMonitorStruct *)malloc(sizeof(MsgMonitorStruct) * MAX_MSG_MONITORS)))
  13960.         return; // Yield the default return value set earlier.
  13961.  
  13962.     // Check if this message already exists in the array:
  13963.     int msg_index;
  13964.     for (msg_index = 0; msg_index < g_MsgMonitorCount; ++msg_index)
  13965.         if (g_MsgMonitor[msg_index].msg == specified_msg)
  13966.             break;
  13967.     bool item_already_exists = (msg_index < g_MsgMonitorCount);
  13968.     MsgMonitorStruct &monitor = g_MsgMonitor[msg_index == MAX_MSG_MONITORS ? 0 : msg_index]; // The 0th item is just a placeholder.
  13969.  
  13970.     if (item_already_exists)
  13971.     {
  13972.         // In all cases, yield the OLD function's name as the return value:
  13973.         strcpy(buf, monitor.func->mName); // Caller has ensured that buf large enough to support max function name.
  13974.         aResultToken.marker = buf;
  13975.         if (mode_is_delete)
  13976.         {
  13977.             // The msg-monitor is deleted from the array for two reasons:
  13978.             // 1) It improves performance because every incoming message for the app now needs to be compared
  13979.             //    to one less filter. If the count will now be zero, performance is improved even more because
  13980.             //    the overhead of the call to MsgMonitor() is completely avoided for every incoming message.
  13981.             // 2) It conserves space in the array in a situation where the script creates hundreds of
  13982.             //    msg-monitors and then later deletes them, then later creates hundreds of filters for entirely
  13983.             //    different message numbers.
  13984.             // The main disadvantage to deleting message filters from the array is that the deletion might
  13985.             // occur while the monitor is currently running, which requires more complex handling within
  13986.             // MsgMonitor() (see its comments for details).
  13987.             --g_MsgMonitorCount;  // Must be done prior to the below.
  13988.             if (msg_index < g_MsgMonitorCount) // An element other than the last is being removed. Shift the array to cover/delete it.
  13989.                 MoveMemory(g_MsgMonitor+msg_index, g_MsgMonitor+msg_index+1, sizeof(MsgMonitorStruct)*(g_MsgMonitorCount-msg_index));
  13990.             return;
  13991.         }
  13992.         if (aParamCount < 2) // Single-parameter mode: Report existing item's function name.
  13993.             return; // Everything was already set up above to yield the proper return value.
  13994.         // Otherwise, an existing item is being assigned a new function (the function has already
  13995.         // been verified valid above). Continue on to update this item's attributes.
  13996.     }
  13997.     else // This message doesn't exist in array yet.
  13998.     {
  13999.         if (mode_is_delete || aParamCount < 2) // Delete or report function-name of a non-existent item.
  14000.             return; // Yield the default return value set earlier (an empty string).
  14001.         // Since above didn't return, the message is to be added as a new element. The above already
  14002.         // verified that func is not NULL.
  14003.         if (msg_index == MAX_MSG_MONITORS) // No room in array.
  14004.             return; // Indicate failure by yielding the default return value set earlier.
  14005.         // Otherwise, the message is to be added, so increment the total:
  14006.         ++g_MsgMonitorCount;
  14007.         strcpy(buf, func->mName); // Yield the NEW name as an indicator of success. Caller has ensured that buf large enough to support max function name.
  14008.         aResultToken.marker = buf;
  14009.         monitor.instance_count = 0; // Reset instance_count only for new items since existing items might currently be running.
  14010.         // Continue on to the update-or-create logic below.
  14011.     }
  14012.  
  14013.     // Since above didn't return, above has ensured that msg_index is the index of the existing or new
  14014.     // MsgMonitorStruct in the array.  In addition, it has set the proper return value for us.
  14015.     // Update those struct attributes that get the same treatment regardless of whether this is an update or creation.
  14016.     monitor.msg = specified_msg;
  14017.     monitor.func = func;
  14018.     if (aParamCount > 2)
  14019.         monitor.max_instances = (short)ExprTokenToInt64(*aParam[2]); // No validation because it seems harmless if it's negative or some huge number.
  14020.     else // Unspecified, so if this item is being newly created fall back to the default.
  14021.         if (!item_already_exists)
  14022.             monitor.max_instances = 1;
  14023. }
  14024.  
  14025.  
  14026.  
  14027. struct RCCallbackFunc // Used by BIF_RegisterCallback() and related.
  14028. {
  14029.     ULONG data1;    //E8 00 00 00
  14030.     ULONG data2;    //00 8D 44 24
  14031.     ULONG data3;    //08 50 FF 15
  14032.     UINT (__stdcall **callfuncptr)(UINT*,char*);
  14033.     ULONG data4;    //59 84 C4 nn
  14034.     USHORT data5;    //FF E1
  14035.     //code ends
  14036.     UCHAR actual_param_count; // This is the actual (not formal) number of parameters passed from the caller to the callback. Kept adjacent to the USHORT above to conserve memory due to 4-byte struct alignment.
  14037.     bool create_new_thread; // Kept adjacent to above to conserve memory due to 4-byte struct alignment.
  14038.     DWORD event_info; // A_EventInfo
  14039.     Func *func; // The UDF to be called whenever the callback's caller calls callfuncptr.
  14040. };
  14041.  
  14042.  
  14043.  
  14044. UINT __stdcall RegisterCallbackCStub(UINT *params, char *address) // Used by BIF_RegisterCallback().
  14045. // JGR: On Win32 parameters are always 4 bytes wide. The exceptions are functions which work on the FPU stack
  14046. // (not many of those). Win32 is quite picky about the stack always being 4 byte-aligned, (I have seen only one
  14047. // application which defied that and it was a patched ported DOS mixed mode application). The Win32 calling
  14048. // convention assumes that the parameter size equals the pointer size. 64 integers on Win32 are passed on
  14049. // pointers, or as two 32 bit halves for some functionsà
  14050. {
  14051.     #define DEFAULT_CB_RETURN_VALUE 0  // The value returned to the callback's caller if script doesn't provide one.
  14052.  
  14053.     RCCallbackFunc &cb = *((RCCallbackFunc*)(address-5)); //second instruction is 5 bytes after start (return address pushed by call)
  14054.     Func &func = *cb.func; // For performance and convenience.
  14055.  
  14056.     // NOTES ABOUT INTERRUPTIONS / CRITICAL:
  14057.     // An incoming call to a callback is considered an "emergency" for the purpose of determining whether
  14058.     // critical/high-priority threads should be interrupted because there's no way easy way to buffer or
  14059.     // postpone the call.  Therefore, NO check of the following is done here:
  14060.     // - Current thread's priority (that's something of a deprecated feature anyway).
  14061.     // - Current thread's status of Critical (however, Critical would prevent us from ever being called in
  14062.     //   cases where the callback is triggered indirectly via message/dispatch due to message filtering
  14063.     //   and/or Critical's ability to pump messes less often).
  14064.     // - INTERRUPTIBLE_IN_EMERGENCY (which includes g_MenuIsVisible and g_AllowInterruption), which primarily
  14065.     //   affects SLEEP_WITHOUT_INTERRUPTION): It's debatable, but to maximize flexibility it seems best to allow
  14066.     //   callbacks during the display of a menu and during SLEEP_WITHOUT_INTERRUPTION.  For most callers of
  14067.     //   SLEEP_WITHOUT_INTERRUPTION, interruptions seem harmless.  For some it could be a problem, but when you
  14068.     //   consider how rare such callbacks are (mostly just subclassing of windows/controls) and what those
  14069.     //   callbacks tend to do, conflicts seem very rare.
  14070.     // Of course, a callback can also be triggered through explicit script action such as a DllCall of
  14071.     // EnumWindows, in which case the script would want to be interrupted unconditionally to make the call.
  14072.     // However, in those cases it's hard to imagine that INTERRUPTIBLE_IN_EMERGENCY wouldn't be true anyway.
  14073.     if (cb.create_new_thread && g_nThreads >= g_MaxThreadsTotal) // Since this is a callback, it seems too rare to make an exemption for functions whose first command is ExitApp.
  14074.         return DEFAULT_CB_RETURN_VALUE;
  14075.  
  14076.     // Need to check if backup of function's variables is needed in case:
  14077.     // 1) The UDF is assigned to more than one callback, in which case the UDF could be running more than one
  14078.     //    simultantously.
  14079.     // 2) The callback is intended to be reentrant (e.g. a subclass/WindowProc that doesn't Critical).
  14080.     // 3) Script explicitly calls the UDF in addition to using it as a callback.
  14081.     //
  14082.     // See ExpandExpression() for detailed comments about the following section.
  14083.     VarBkp *var_backup = NULL;  // If needed, it will hold an array of VarBkp objects.
  14084.     int var_backup_count; // The number of items in the above array.
  14085.     if (func.mInstances > 0) // Backup is needed.
  14086.         if (!Var::BackupFunctionVars(func, var_backup, var_backup_count)) // Out of memory.
  14087.             return DEFAULT_CB_RETURN_VALUE; // Since out-of-memory is so rare, it seems justifiable not to have any error reporting and instead just avoid calling the function.
  14088.  
  14089.     global_struct global_saved;
  14090.     char ErrorLevel_saved[ERRORLEVEL_SAVED_SIZE];
  14091.     DWORD EventInfo_saved;
  14092.     if (cb.create_new_thread)
  14093.     {
  14094.         // See MsgSleep() for comments about the following section.
  14095.         strlcpy(ErrorLevel_saved, g_ErrorLevel->Contents(), sizeof(ErrorLevel_saved));
  14096.         CopyMemory(&global_saved, &g, sizeof(global_struct));
  14097.         if (g_nFileDialogs) // If a FileSelectFile dialog is present, ensure the new thread starts at the right working-dir.
  14098.             SetCurrentDirectory(g_WorkingDir); // See MsgSleep() for details.
  14099.         InitNewThread(0, false, true, func.mJumpToLine->mActionType);
  14100.  
  14101.     }
  14102.     else // Backup/restore only A_EventInfo. This avoids callbacks changing A_EventInfo for the current thread/context (that would be counterintuitive and a source of script bugs).
  14103.         EventInfo_saved = g.EventInfo;
  14104.  
  14105.     g.EventInfo = cb.event_info; // This is the means to identify which caller called the callback (if the script assigned more than one caller to this callback).
  14106.     g_script.UpdateTrayIcon(); // Doesn't measurably impact performance (unless icon needs to be changed, which it generally won't in the case of fast-mode because by definition the current thread isn't paused). This is necessary because it's possible the tray icon shows "paused" if this callback was called via message (e.g. when subclassing a control).
  14107.  
  14108.     // The following section is similar to the one in ExpandExpression().  See it for detailed comments.
  14109.     int i;
  14110.     for (i = 0; i < cb.actual_param_count; ++i)  // For each formal parameter that has a matching actual (an earlier stage already verified that there are enough formals to cover the actuals).
  14111.         func.mParam[i].var->Assign((DWORD)params[i]); // All parameters are passed "by value" because an earlier stage ensured there are no ByRef parameters.
  14112.     for (; i < func.mParamCount; ++i) // For each remaining formal (i.e. those that lack actuals), apply a default value (an earlier stage verified that all such parameters have a default-value available).
  14113.     {
  14114.         FuncParam &this_formal_param = func.mParam[i]; // For performance and convenience.
  14115.         // The following isn't necessary because an earlier stage has already ensured that there
  14116.         // are no ByRef paramaters in a callback:
  14117.         //if (this_formal_param.is_byref)
  14118.         //    this_formal_param.var->ConvertToNonAliasIfNecessary();
  14119.         switch(this_formal_param.default_type)
  14120.         {
  14121.         case PARAM_DEFAULT_STR:   this_formal_param.var->Assign(this_formal_param.default_str);    break;
  14122.         case PARAM_DEFAULT_INT:   this_formal_param.var->Assign(this_formal_param.default_int64);  break;
  14123.         case PARAM_DEFAULT_FLOAT: this_formal_param.var->Assign(this_formal_param.default_double); break;
  14124.         //case PARAM_DEFAULT_NONE: Not possible due to validation at an earlier stage.
  14125.         }
  14126.     }
  14127.  
  14128.     g_script.mLastScriptRest = g_script.mLastPeekTime = GetTickCount(); // Somewhat debatable, but might help minimize interruptions when the callback is called via message (e.g. subclassing a control; overriding a WindowProc).
  14129.  
  14130.     char *return_value;
  14131.     func.Call(return_value); // Call the UDF.
  14132.  
  14133.     // MUST handle return_value BEFORE calling FreeAndRestoreFunctionVars() because return_value might be
  14134.     // the contents of one of the function's local variables (which are about to be free'd).
  14135.     UINT number_to_return = *return_value ? ATOU(return_value) : DEFAULT_CB_RETURN_VALUE; // No need to check the following because they're implied for *return_value!=0: result != EARLY_EXIT && result != FAIL;
  14136.     Var::FreeAndRestoreFunctionVars(func, var_backup, var_backup_count);
  14137.  
  14138.     if (cb.create_new_thread)
  14139.         ResumeUnderlyingThread(&global_saved, ErrorLevel_saved, true);
  14140.     else
  14141.         g.EventInfo = EventInfo_saved;
  14142.  
  14143.     return number_to_return; //return integer value to callback stub
  14144. }
  14145.  
  14146.  
  14147.  
  14148. void BIF_RegisterCallback(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  14149. // Returns: Address of callback procedure, or empty string on failure.
  14150. // Parameters:
  14151. // 1: Name of the function to be called when the callback routine is executed.
  14152. // 2: Options.
  14153. // 3: Number of parameters of callback.
  14154. // 4: EventInfo: a DWORD set for use by UDF to identify the caller (in case more than one caller).
  14155. //
  14156. // Author: RegisterCallback() was created by Jonathan Rennison (JGR).
  14157. {
  14158.     // Set default result in case of early return; a blank value:
  14159.     aResultToken.symbol = SYM_STRING;
  14160.     aResultToken.marker = "";
  14161.  
  14162.     // Loadtime validation has ensured that at least 1 parameter is present.
  14163.     char func_buf[MAX_FORMATTED_NUMBER_LENGTH + 1], *func_name;
  14164.     Func *func;
  14165.     if (   !*(func_name = ExprTokenToString(*aParam[0], func_buf))  // Blank function name or...
  14166.         || !(func = g_script.FindFunc(func_name))  // ...the function doesn't exist or...
  14167.         || func->mIsBuiltIn   )  // ...the function is built-in.
  14168.         return; // Indicate failure by yielding the default result set earlier.
  14169.  
  14170.     char options_buf[MAX_FORMATTED_NUMBER_LENGTH + 1];
  14171.     char *options = (aParamCount < 2) ? "" : ExprTokenToString(*aParam[1], options_buf);
  14172.  
  14173.     int actual_param_count;
  14174.     if (aParamCount > 2) // A parameter count was specified.
  14175.     {
  14176.         actual_param_count = (int)ExprTokenToInt64(*aParam[2]);
  14177.         if (   actual_param_count > func->mParamCount    // The function doesn't have enough formals to cover the specified number of actuals.
  14178.             || actual_param_count < func->mMinParams   ) // ...or the function has too many mandatory formals (caller specified insufficient actuals to cover them all).
  14179.             return; // Indicate failure by yielding the default result set earlier.
  14180.     }
  14181.     else // Default to the number of mandatory formal parameters in the function's definition.
  14182.         actual_param_count = func->mMinParams;
  14183.  
  14184.     if (actual_param_count > 31) // The ASM instruction currently used limits parameters to 31 (which should be plenty for any realistic use).
  14185.         return; // Indicate failure by yielding the default result set earlier.
  14186.  
  14187.     // To improve callback performance, ensure there are no ByRef parameters (for simplicity: not even ones that
  14188.     // have default values).  This avoids the need to ensure formal parameters are non-aliases each time the
  14189.     // callback is called.
  14190.     for (int i = 0; i < func->mParamCount; ++i)
  14191.         if (func->mParam[i].is_byref)
  14192.             return; // Yield the default return value set earlier.
  14193.  
  14194.     // GlobalAlloc() and dynamically-built code is the means by which a script can have an unlimited number of
  14195.     // distinct callbacks. On Win32, GlobalAlloc is the same function as LocalAlloc: they both point to
  14196.     // RtlAllocateHeap on the process heap. For large chunks of code you would reserve a 64K section with
  14197.     // VirtualAlloc and fill that, but for the 32 bytes we use here that would be overkill; GlobalAlloc is
  14198.     // much more efficient. MSDN says about GlobalAlloc: "All memory is created with execute access; no
  14199.     // special function is required to execute dynamically generated code. Memory allocated with this function
  14200.     // is guaranteed to be aligned on an 8-byte boundary." 
  14201.     RCCallbackFunc *callbackfunc=(RCCallbackFunc*) GlobalAlloc(GMEM_FIXED,sizeof(RCCallbackFunc));    //allocate structure off process heap, automatically RWE and fixed.
  14202.     if(!callbackfunc) return;
  14203.     RCCallbackFunc &cb = *callbackfunc; // For convenience and possible code-size reduction.
  14204.  
  14205.     cb.data1=0xE8;       // call +0 -- E8 00 00 00 00 ;get eip, stays on stack as parameter 2 for C function (char *address).
  14206.     cb.data2=0x24448D00; // lea eax, [esp+8] -- 8D 44 24 08 ;eax points to params
  14207.     cb.data3=0x15FF5008; // push eax -- 50 ;eax pushed on stack as parameter 1 for C stub (UINT *params)
  14208.                          // call [xxxx] (in the lines below) -- FF 15 xx xx xx xx ;call C stub __stdcall, so stack cleaned up for us.
  14209.  
  14210.     // Comments about the static variable below: The reason for using the address of a pointer to a function,
  14211.     // is that the address is passed as a fixed address, whereas a direct call is passed as a 32-bit offset
  14212.     // relative to the beginning of the next instruction, which is more fiddly than it's worth to calculate
  14213.     // for dynamic code, as a relative call is designed to allow position independent calls to within the
  14214.     // same memory block without requiring dynamic fixups, or other such inconveniences.  In essence:
  14215.     //    call xxx ; is relative
  14216.     //    call [ptr_xxx] ; is position independent
  14217.     // Typically the latter is used when calling imported functions, etc., as only the pointers (import table),
  14218.     // need to be adjusted, not the calls themselves...
  14219.     static UINT (__stdcall *funcaddrptr)(UINT*,char*)=RegisterCallbackCStub; // Use fixed absolute address of pointer to function, instead of varying relative offset to function.
  14220.     cb.callfuncptr=&funcaddrptr; // xxxx: Address of C stub.
  14221.  
  14222.     cb.data4=0xC48359 // pop ecx -- 59 ;return address... add esp, xx -- 83 C4 xx ;stack correct (add argument to add esp, nn for stack correction).
  14223.         + (StrChrAny(options, "Cc") ? 0 : actual_param_count<<26);  // Recognize "C" as the "CDecl" option.
  14224.  
  14225.     cb.data5=0xE1FF; // jmp ecx -- FF E1 ;return
  14226.  
  14227.     cb.event_info = (aParamCount < 4) ? (DWORD)(size_t)callbackfunc : (DWORD)ExprTokenToInt64(*aParam[3]);
  14228.     cb.func = func;
  14229.     cb.actual_param_count = actual_param_count;
  14230.     cb.create_new_thread = !StrChrAny(options, "Ff"); // Recognize "F" as the "fast" mode that avoids creating a new thread.
  14231.  
  14232.     aResultToken.symbol = SYM_INTEGER; // Override the default set earlier.
  14233.     aResultToken.value_int64 = (__int64)callbackfunc; // Yield the callable address as the result.
  14234. }
  14235.  
  14236.  
  14237.  
  14238. void BIF_StatusBar(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  14239. {
  14240.     char mode = toupper(aResultToken.marker[6]); // Union's marker initially contains the function name. SB_Set[T]ext.
  14241.     char *buf = aResultToken.buf; // Must be saved early since below overwrites the union (better maintainability too).
  14242.     aResultToken.value_int64 = 0; // Set default return value. Must be done only after consulting marker above.
  14243.     // Above sets default result in case of early return.  For code reduction, a zero is returned for all
  14244.     // the following conditions:
  14245.     // Window doesn't exist.
  14246.     // Control doesn't exist (i.e. no StatusBar in window).
  14247.  
  14248.     if (!g_gui[g.GuiDefaultWindowIndex])
  14249.         return;
  14250.     GuiType &gui = *g_gui[g.GuiDefaultWindowIndex]; // Always operate on thread's default window to simplify the syntax.
  14251.     HWND control_hwnd;
  14252.     if (   !(control_hwnd = gui.mStatusBarHwnd)   )
  14253.         return;
  14254.  
  14255.     HICON hicon;
  14256.     switch(mode)
  14257.     {
  14258.     case 'T': // SB_SetText()
  14259.         aResultToken.value_int64 = SendMessage(control_hwnd, SB_SETTEXT
  14260.             , (WPARAM)((aParamCount < 2 ? 0 : ExprTokenToInt64(*aParam[1]) - 1) // The Part# param is present.
  14261.                 | (aParamCount < 3 ? 0 : ExprTokenToInt64(*aParam[2]) << 8)) // The uType parameter is present.
  14262.             , (LPARAM)ExprTokenToString(*aParam[0], buf)); // Load-time validation has ensured that there's at least one param in this mode.
  14263.         break;
  14264.  
  14265.     case 'P': // SB_SetParts()
  14266.         LRESULT old_part_count, new_part_count;
  14267.         int edge, part[256]; // Load-time validation has ensured aParamCount is under 255, so it shouldn't overflow.
  14268.         for (edge = 0, new_part_count = 0; new_part_count < aParamCount; ++new_part_count)
  14269.         {
  14270.             edge += (int)ExprTokenToInt64(*aParam[new_part_count]); // For code simplicity, no check for negative (seems fairly harmless since the bar will simply show up with the wrong number of parts to indicate the problem).
  14271.             part[new_part_count] = edge;
  14272.         }
  14273.         // For code simplicity, there is currently no means to have the last part of the bar use less than
  14274.         // all of the bar's remaining width.  The desire to do so seems rare, especially since the script can
  14275.         // add an extra/unused part at the end to achieve nearly (or perhaps exactly) the same effect.
  14276.         part[new_part_count++] = -1; // Make the last part use the remaining width of the bar.
  14277.  
  14278.         old_part_count = SendMessage(control_hwnd, SB_GETPARTS, 0, NULL); // MSDN: "This message always returns the number of parts in the status bar [regardless of how it is called]".
  14279.         if (old_part_count > new_part_count) // Some parts are being deleted, so destroy their icons.  See other notes in GuiType::Destroy() for explanation.
  14280.             for (LRESULT i = new_part_count; i < old_part_count; ++i) // Verified correct.
  14281.                 if (hicon = (HICON)SendMessage(control_hwnd, SB_GETICON, i, 0))
  14282.                     DestroyIcon(hicon);
  14283.  
  14284.         aResultToken.value_int64 = SendMessage(control_hwnd, SB_SETPARTS, new_part_count, (LPARAM)part)
  14285.             ? (__int64)control_hwnd : 0; // Return HWND to provide an easy means for the script to get the bar's HWND.
  14286.         break;
  14287.  
  14288.     case 'I': // SB_SetIcon()
  14289.         int unused, icon_number;
  14290.         icon_number = (aParamCount < 2) ? 1 : (int)ExprTokenToInt64(*aParam[1]);
  14291.         if (icon_number < 1) // Must be >0 to tell LoadPicture that "icon must be loaded, never a bitmap".
  14292.             icon_number = 1;
  14293.         if (hicon = (HICON)LoadPicture(ExprTokenToString(*aParam[0], buf) // Load-time validation has ensured there is at least one parameter.
  14294.             , GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON) // Apparently the bar won't scale them for us.
  14295.             , unused, icon_number, false)) // Defaulting to "false" for "use GDIplus" provides more consistent appearance across multiple OSes.
  14296.         {
  14297.             WPARAM part_index = (aParamCount < 3) ? 0 : (WPARAM)ExprTokenToInt64(*aParam[2]) - 1;
  14298.             HICON hicon_old = (HICON)SendMessage(control_hwnd, SB_GETICON, part_index, 0); // Get the old one before setting the new one.
  14299.             // For code simplicity, the script is responsible for destroying the hicon later, if it ever destroys
  14300.             // the window.  Though in practice, most people probably won't do this, which is usually okay (if the
  14301.             // script doesn't load too many) since they're all destroyed by the system upon program termination.
  14302.             if (SendMessage(control_hwnd, SB_SETICON, part_index, (LPARAM)hicon))
  14303.             {
  14304.                 aResultToken.value_int64 = (__int64)hicon; // Override the 0 default. This allows the script to destroy the HICON later when it doesn't need it (see comments above too).
  14305.                 if (hicon_old)
  14306.                     // Although the old icon is automatically destroyed here, the script can call SendMessage(SB_SETICON)
  14307.                     // itself if it wants to work with HICONs directly (for performance reasons, etc.)
  14308.                     DestroyIcon(hicon_old);
  14309.             }
  14310.             else
  14311.                 DestroyIcon(hicon);
  14312.                 //And leave aResultToken.value_int64 at its default value.
  14313.         }
  14314.         //else can't load icon, so leave aResultToken.value_int64 at its default value.
  14315.         break;
  14316.     // SB_SetTipText() not implemented (though can be done via SendMessage in the script) because the conditions
  14317.     // under which tooltips are displayed don't seem like something a script would want very often:
  14318.     // This ToolTip text is displayed in two situations: 
  14319.     // When the corresponding pane in the status bar contains only an icon. 
  14320.     // When the corresponding pane in the status bar contains text that is truncated due to the size of the pane.
  14321.     // In spite of the above, SB_SETTIPTEXT doesn't actually seem to do anything, even when the text is too long
  14322.     // to fit in a narrowed part, tooltip text has been set, and the user hovers the cursor over the bar.  Maybe
  14323.     // I'm not doing it right or maybe this feature is somehow disabled under certain service packs or conditions.
  14324.     //case 'T': // SB_SetTipText()
  14325.     //    break;
  14326.     } // switch(mode)
  14327. }
  14328.  
  14329.  
  14330.  
  14331. void BIF_LV_GetNextOrCount(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  14332. // LV_GetNext:
  14333. // Returns: The index of the found item, or 0 on failure.
  14334. // Parameters:
  14335. // 1: Starting index (one-based when it comes in).  If absent, search starts at the top.
  14336. // 2: Options string.
  14337. // 3: (FUTURE): Possible for use with LV_FindItem (though I think it can only search item text, not subitem text).
  14338. {
  14339.     bool mode_is_count = toupper(aResultToken.marker[6]) == 'C'; // Union's marker initially contains the function name. LV_Get[C]ount.  Bug-fixed for v1.0.43.09.
  14340.     char *buf = aResultToken.buf; // Must be saved early since below overwrites the union (better maintainability too).
  14341.     aResultToken.value_int64 = 0; // Set default return value. Must be done only after consulting marker above.
  14342.     // Above sets default result in case of early return.  For code reduction, a zero is returned for all
  14343.     // the following conditions:
  14344.     // Window doesn't exist.
  14345.     // Control doesn't exist (i.e. no ListView in window).
  14346.     // Item not found in ListView.
  14347.  
  14348.     if (!g_gui[g.GuiDefaultWindowIndex])
  14349.         return;
  14350.     GuiType &gui = *g_gui[g.GuiDefaultWindowIndex]; // Always operate on thread's default window to simplify the syntax.
  14351.     if (!gui.mCurrentListView)
  14352.         return;
  14353.     HWND control_hwnd = gui.mCurrentListView->hwnd;
  14354.  
  14355.     char *options;
  14356.     if (mode_is_count)
  14357.     {
  14358.         options = (aParamCount > 0) ? omit_leading_whitespace(ExprTokenToString(*aParam[0], buf)) : "";
  14359.         if (*options)
  14360.         {
  14361.             if (toupper(*options) == 'S')
  14362.                 aResultToken.value_int64 = SendMessage(control_hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
  14363.             else if (!strnicmp(options, "Col", 3)) // "Col" or "Column". Don't allow "C" by itself, so that "Checked" can be added in the future.
  14364.                 aResultToken.value_int64 = gui.mCurrentListView->union_lv_attrib->col_count;
  14365.             //else some unsupported value, leave aResultToken.value_int64 set to zero to indicate failure.
  14366.         }
  14367.         else
  14368.             aResultToken.value_int64 = SendMessage(control_hwnd, LVM_GETITEMCOUNT, 0, 0);
  14369.         return;
  14370.     }
  14371.     // Since above didn't return, this is GetNext() mode.
  14372.  
  14373.     int index = (int)((aParamCount > 0) ? ExprTokenToInt64(*aParam[0]) - 1 : -1); // -1 to convert to zero-based.
  14374.     // For flexibility, allow index to be less than -1 to avoid first-iteration complications in script loops
  14375.     // (such as when deleting rows, which shifts the row index upward, require the search to resume at
  14376.     // the previously found index rather than the row after it).  However, reset it to -1 to ensure
  14377.     // proper return values from the API in the "find checked item" mode used below.
  14378.     if (index < -1)
  14379.         index = -1;  // Signal it to start at the top.
  14380.  
  14381.     // For performance, decided to always find next selected item when the "C" option hasn't been specified,
  14382.     // even when the checkboxes style is in effect.  Otherwise, would have to fetch and check checkbox style
  14383.     // bit for each call, which would slow down this heavily-called function.
  14384.  
  14385.     options = (aParamCount > 1) ? ExprTokenToString(*aParam[1], buf) : "";
  14386.     char first_char = toupper(*omit_leading_whitespace(options));
  14387.     // To retain compatibility in the future, also allow "Check(ed)" and "Focus(ed)" since any word that
  14388.     // starts with C or F is already supported.
  14389.  
  14390.     switch(first_char)
  14391.     {
  14392.     case '\0': // Listed first for performance.
  14393.     case 'F':
  14394.         aResultToken.value_int64 = ListView_GetNextItem(control_hwnd, index
  14395.             , first_char ? LVNI_FOCUSED : LVNI_SELECTED) + 1; // +1 to convert to 1-based.
  14396.         break;
  14397.     case 'C': // Checkbox: Find checked items. For performance assume that the control really has checkboxes.
  14398.         int item_count = ListView_GetItemCount(control_hwnd);
  14399.         for (int i = index + 1; i < item_count; ++i) // Start at index+1 to omit the first item from the search (for consistency with the other mode above).
  14400.             if (ListView_GetCheckState(control_hwnd, i)) // Item's box is checked.
  14401.             {
  14402.                 aResultToken.value_int64 = i + 1; // +1 to convert from zero-based to one-based.
  14403.                 return;
  14404.             }
  14405.         // Since above didn't return, no match found.  The 0/false value previously set as the default is retained.
  14406.         break;
  14407.     }
  14408. }
  14409.  
  14410.  
  14411.  
  14412. void BIF_LV_GetText(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  14413. // Returns: 1 on success and 0 on failure.
  14414. // Parameters:
  14415. // 1: Output variable (doing it this way allows success/fail return value to more closely mirror the API and
  14416. //    simplifies the code since there is currently no easy means of passing back large strings to our caller).
  14417. // 2: Row index (one-based when it comes in).
  14418. // 3: Column index (one-based when it comes in).
  14419. {
  14420.     aResultToken.value_int64 = 0; // Set default return value.
  14421.     // Above sets default result in case of early return.  For code reduction, a zero is returned for all
  14422.     // the following conditions:
  14423.     // Window doesn't exist.
  14424.     // Control doesn't exist (i.e. no ListView in window).
  14425.     // Item not found in ListView.
  14426.     // And others.
  14427.  
  14428.     if (!g_gui[g.GuiDefaultWindowIndex])
  14429.         return;
  14430.     GuiType &gui = *g_gui[g.GuiDefaultWindowIndex]; // Always operate on thread's default window to simplify the syntax.
  14431.     if (!gui.mCurrentListView)
  14432.         return;
  14433.     // Caller has ensured there is at least two parameters:
  14434.     if (aParam[0]->symbol != SYM_VAR) // No output variable.  Supporting a NULL for the purpose of checking for the existence of a cell seems too rarely needed.
  14435.         return;
  14436.  
  14437.     // Caller has ensured there is at least two parameters.
  14438.     int row_index = (int)ExprTokenToInt64(*aParam[1]) - 1; // -1 to convert to zero-based.
  14439.     // If parameter 3 is omitted, default to the first column (index 0):
  14440.     int col_index = (aParamCount > 2) ? (int)ExprTokenToInt64(*aParam[2]) - 1 : 0; // -1 to convert to zero-based.
  14441.     if (row_index < -1 || col_index < 0) // row_index==-1 is reserved to mean "get column heading's text".
  14442.         return;
  14443.  
  14444.     Var &output_var = *aParam[0]->var; // It was already ensured higher above that symbol==SYM_VAR.
  14445.     char buf[LV_TEXT_BUF_SIZE];
  14446.  
  14447.     if (row_index == -1) // Special mode to get column's text.
  14448.     {
  14449.         LVCOLUMN lvc;
  14450.         lvc.cchTextMax = LV_TEXT_BUF_SIZE - 1;  // See notes below about -1.
  14451.         lvc.pszText = buf;
  14452.         lvc.mask = LVCF_TEXT;
  14453.         if (aResultToken.value_int64 = SendMessage(gui.mCurrentListView->hwnd, LVM_GETCOLUMN, col_index, (LPARAM)&lvc)) // Assign.
  14454.             output_var.Assign(lvc.pszText); // See notes below about why pszText is used instead of buf (might apply to this too).
  14455.         else // On failure, it seems best to also clear the output var for better consistency and in case the script doesn't check the return value.
  14456.             output_var.Assign();
  14457.     }
  14458.     else // Get row's indicated item or subitem text.
  14459.     {
  14460.         LVITEM lvi;
  14461.         // Subtract 1 because of that nagging doubt about size vs. length. Some MSDN examples subtract one, such as
  14462.         // TabCtrl_GetItem()'s cchTextMax:
  14463.         lvi.iItem = row_index;
  14464.         lvi.iSubItem = col_index; // Which field to fetch.  If it's zero, the item vs. subitem will be fetched.
  14465.         lvi.mask = LVIF_TEXT;
  14466.         lvi.pszText = buf;
  14467.         lvi.cchTextMax = LV_TEXT_BUF_SIZE - 1; // Note that LVM_GETITEM doesn't update this member to reflect the new length.
  14468.         // Unlike LVM_GETITEMTEXT, LVM_GETITEM indicates success or failure, which seems more useful/preferable
  14469.         // as a return value since a text length of zero would be ambiguous: could be an empty field or a failure.
  14470.         if (aResultToken.value_int64 = SendMessage(gui.mCurrentListView->hwnd, LVM_GETITEM, 0, (LPARAM)&lvi)) // Assign
  14471.             // Must use lvi.pszText vs. buf because MSDN says: "Applications should not assume that the text will
  14472.             // necessarily be placed in the specified buffer. The control may instead change the pszText member
  14473.             // of the structure to point to the new text rather than place it in the buffer."
  14474.             output_var.Assign(lvi.pszText);
  14475.         else // On failure, it seems best to also clear the output var for better consistency and in case the script doesn't check the return value.
  14476.             output_var.Assign();
  14477.     }
  14478. }
  14479.  
  14480.  
  14481.  
  14482. void BIF_LV_AddInsertModify(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  14483. // Returns: 1 on success and 0 on failure.
  14484. // Parameters:
  14485. // 1: For Add(), this is the options.  For Insert/Modify, it's the row index (one-based when it comes in).
  14486. // 2: For Add(), this is the first field's text.  For Insert/Modify, it's the options.
  14487. // 3 and beyond: Additional field text.
  14488. // In Add/Insert mode, if there are no text fields present, a blank for is appended/inserted.
  14489. {
  14490.     char mode = toupper(aResultToken.marker[3]); // Union's marker initially contains the function name. e.g. LV_[I]nsert.
  14491.     char *buf = aResultToken.buf; // Must be saved early since below overwrites the union (better maintainability too).
  14492.     aResultToken.value_int64 = 0; // Set default return value. Must be done only after consulting marker above.
  14493.     // Above sets default result in case of early return.  For code reduction, a zero is returned for all
  14494.     // the following conditions:
  14495.     // Window doesn't exist.
  14496.     // Control doesn't exist (i.e. no ListView in window).
  14497.     // And others as shown below.
  14498.  
  14499.     int index;
  14500.     if (mode == 'A') // For Add mode (A), use INT_MAX as a signal to append the item rather than inserting it.
  14501.     {
  14502.         index = INT_MAX;
  14503.         mode = 'I'; // Add has now been set up to be the same as insert, so change the mode to simplify other things.
  14504.     }
  14505.     else // Insert or Modify: the target row-index is their first parameter, which load-time has ensured is present.
  14506.     {
  14507.         index = (int)ExprTokenToInt64(*aParam[0]) - 1; // -1 to convert to zero-based.
  14508.         if (index < -1 || (mode != 'M' && index < 0)) // Allow -1 to mean "all rows" when in modify mode.
  14509.             return;
  14510.         ++aParam;  // Remove the first parameter from further consideration to make Insert/Modify symmetric with Add.
  14511.         --aParamCount;
  14512.     }
  14513.  
  14514.     if (!g_gui[g.GuiDefaultWindowIndex])
  14515.         return;
  14516.     GuiType &gui = *g_gui[g.GuiDefaultWindowIndex]; // Always operate on thread's default window to simplify the syntax.
  14517.     if (!gui.mCurrentListView)
  14518.         return;
  14519.     GuiControlType &control = *gui.mCurrentListView;
  14520.  
  14521.     char *options = (aParamCount > 0) ? ExprTokenToString(*aParam[0], buf) : "";
  14522.     bool ensure_visible = false, is_checked = false;  // Checkmark.
  14523.     int col_start_index = 0;
  14524.     LVITEM lvi;
  14525.     lvi.mask = LVIF_STATE; // LVIF_STATE: state member is valid, but only to the extent that corresponding bits are set in stateMask (the rest will be ignored).
  14526.     lvi.stateMask = 0;
  14527.     lvi.state = 0;
  14528.  
  14529.     // Parse list of space-delimited options:
  14530.     char *next_option, *option_end, orig_char;
  14531.     bool adding; // Whether this option is beeing added (+) or removed (-).
  14532.  
  14533.     for (next_option = options; *next_option; next_option = omit_leading_whitespace(option_end))
  14534.     {
  14535.         if (*next_option == '-')
  14536.         {
  14537.             adding = false;
  14538.             // omit_leading_whitespace() is not called, which enforces the fact that the option word must
  14539.             // immediately follow the +/- sign.  This is done to allow the flexibility to have options
  14540.             // omit the plus/minus sign, and also to reserve more flexibility for future option formats.
  14541.             ++next_option;  // Point it to the option word itself.
  14542.         }
  14543.         else
  14544.         {
  14545.             // Assume option is being added in the absence of either sign.  However, when we were
  14546.             // called by GuiControl(), the first option in the list must begin with +/- otherwise the cmd
  14547.             // would never have been properly detected as GUICONTROL_CMD_OPTIONS in the first place.
  14548.             adding = true;
  14549.             if (*next_option == '+')
  14550.                 ++next_option;  // Point it to the option word itself.
  14551.             //else do not increment, under the assumption that the plus has been omitted from a valid
  14552.             // option word and is thus an implicit plus.
  14553.         }
  14554.  
  14555.         if (!*next_option) // In case the entire option string ends in a naked + or -.
  14556.             break;
  14557.         // Find the end of this option item:
  14558.         if (   !(option_end = StrChrAny(next_option, " \t"))   )  // Space or tab.
  14559.             option_end = next_option + strlen(next_option); // Set to position of zero terminator instead.
  14560.         if (option_end == next_option)
  14561.             continue; // i.e. the string contains a + or - with a space or tab after it, which is intentionally ignored.
  14562.  
  14563.         // Temporarily terminate to help eliminate ambiguity for words contained inside other words,
  14564.         // such as "Checked" inside of "CheckedGray":
  14565.         orig_char = *option_end;
  14566.         *option_end = '\0';
  14567.  
  14568.         if (!strnicmp(next_option, "Select", 6)) // Could further allow "ed" suffix by checking for that inside, but "Selected" is getting long so it doesn't seem something many would want to use.
  14569.         {
  14570.             next_option += 6;
  14571.             // If it's Select0, invert the mode to become "no select". This allows a boolean variable
  14572.             // to be more easily applied, such as this expression: "Select" . VarContainingState
  14573.             if (*next_option && !ATOI(next_option))
  14574.                 adding = !adding;
  14575.             // Another reason for not having "Select" imply "Focus" by default is that it would probably
  14576.             // reduce performance when selecting all or a large number of rows.
  14577.             // Because a row might or might not have focus, the script may wish to retain its current
  14578.             // focused state.  For this reason, "select" does not imply "focus", which allows the
  14579.             // LVIS_FOCUSED bit to be omitted from the stateMask, which in turn retains the current
  14580.             // focus-state of the row rather than disrupting it.
  14581.             lvi.stateMask |= LVIS_SELECTED;
  14582.             if (adding)
  14583.                 lvi.state |= LVIS_SELECTED;
  14584.             //else removing, so the presence of LVIS_SELECTED in the stateMask above will cause it to be de-selected.
  14585.         }
  14586.         else if (!strnicmp(next_option, "Focus", 5))
  14587.         {
  14588.             next_option += 5;
  14589.             if (*next_option && !ATOI(next_option)) // If it's Focus0, invert the mode to become "no focus".
  14590.                 adding = !adding;
  14591.             lvi.stateMask |= LVIS_FOCUSED;
  14592.             if (adding)
  14593.                 lvi.state |= LVIS_FOCUSED;
  14594.             //else removing, so the presence of LVIS_FOCUSED in the stateMask above will cause it to be de-focused.
  14595.         }
  14596.         else if (!strnicmp(next_option, "Check", 5))
  14597.         {
  14598.             // The rationale for not checking for an optional "ed" suffix here and incrementing next_option by 2
  14599.             // is that: 1) It would be inconsistent with the lack of support for "selected" (see reason above);
  14600.             // 2) Checkboxes in a ListView are fairly rarely used, so code size reduction might be more important.
  14601.             next_option += 5;
  14602.             if (*next_option && !ATOI(next_option)) // If it's Check0, invert the mode to become "unchecked".
  14603.                 adding = !adding;
  14604.             if (mode == 'M') // v1.0.46.10: Do this section only for Modify, not Add/Insert, to avoid generating an extra "unchecked" notification when a row is added/inserted with an initial state of "checked".  In other words, the script now receives only a "checked" notification, not an "unchecked+checked". Search on is_checked for more comments.
  14605.             {
  14606.                 lvi.stateMask |= LVIS_STATEIMAGEMASK;
  14607.                 lvi.state |= adding ? 0x2000 : 0x1000; // The #1 image is "unchecked" and the #2 is "checked".
  14608.             }
  14609.             is_checked = adding;
  14610.         }
  14611.         else if (!strnicmp(next_option, "Col", 3))
  14612.         {
  14613.             if (adding)
  14614.             {
  14615.                 col_start_index = ATOI(next_option + 3) - 1; // The ability start start at a column other than 1 (i.e. subitem vs. item).
  14616.                 if (col_start_index < 0)
  14617.                     col_start_index = 0;
  14618.             }
  14619.         }
  14620.         else if (!strnicmp(next_option, "Icon", 4))
  14621.         {
  14622.             // Testing shows that there is no way to avoid having an item icon in report view if the
  14623.             // ListView has an associated small-icon ImageList (well, perhaps you could have it show
  14624.             // a blank square by specifying an invalid icon index, but that doesn't seem useful).
  14625.             // If LVIF_IMAGE is entirely omitted when adding and item/row, the item will take on the
  14626.             // first icon in the list.  This is probably by design because the control wants to make
  14627.             // each item look consistent by indenting its first field by a certain amount for the icon.
  14628.             if (adding)
  14629.             {
  14630.                 lvi.mask |= LVIF_IMAGE;
  14631.                 lvi.iImage = ATOI(next_option + 4) - 1;  // -1 to convert to zero-based.
  14632.             }
  14633.             //else removal of icon currently not supported (see comment above), so do nothing in order
  14634.             // to reserve "-Icon" in case a future way can be found to do it.
  14635.         }
  14636.         else if (!stricmp(next_option, "Vis")) // v1.0.44
  14637.             // Since this option much more typically used with LV_Modify than LV_Add/Insert, the technique of
  14638.             // Vis%VarContainingOneOrZero% isn't supported, to reduce code size.
  14639.             ensure_visible = adding; // Ignored by modes other than LV_Modify(), since it's not really appropriate when adding a row (plus would add code complexity).
  14640.  
  14641.         // If the item was not handled by the above, ignore it because it is unknown.
  14642.         *option_end = orig_char; // Undo the temporary termination because the caller needs aOptions to be unaltered.
  14643.     }
  14644.  
  14645.     // More maintainable and performs better to have a separate struct for subitems vs. items.
  14646.     LVITEM lvi_sub;
  14647.     // Ensure mask is pure to avoid giving it any excuse to fail due to the fact that
  14648.     // "You cannot set the state or lParam members for subitems."
  14649.     lvi_sub.mask = LVIF_TEXT;
  14650.  
  14651.     int i, j, rows_to_change;
  14652.     if (index == -1) // Modify all rows (above has ensured that this is only happens in modify-mode).
  14653.     {
  14654.         rows_to_change = ListView_GetItemCount(control.hwnd);
  14655.         lvi.iItem = 0;
  14656.         ensure_visible = false; // Not applicable when operating on all rows.
  14657.     }
  14658.     else // Modify or insert a single row.  Set it up for the loop to perform exactly one iteration.
  14659.     {
  14660.         rows_to_change = 1;
  14661.         lvi.iItem = index; // Which row to operate upon.  This can be a huge number such as 999999 if the caller wanted to append vs. insert.
  14662.     }
  14663.     lvi.iSubItem = 0;  // Always zero to operate upon the item vs. sub-item (subitems have their own LVITEM struct).
  14664.     aResultToken.value_int64 = 1; // Set default from this point forward to be true/success. It will be overridden in insert mode to be the index of the new row.
  14665.  
  14666.     for (j = 0; j < rows_to_change; ++j, ++lvi.iItem) // ++lvi.iItem because if the loop has more than one iteration, by definition it is modifying all rows starting at 0.
  14667.     {
  14668.         if (aParamCount > 1 && col_start_index == 0) // 2nd parameter: item's text (first field) is present, so include that when setting the item.
  14669.         {
  14670.             lvi.pszText = ExprTokenToString(*aParam[1], buf); // Fairly low-overhead, so called every iteration for simplicity (so that buf can be used for both items and subitems).
  14671.             lvi.mask |= LVIF_TEXT;
  14672.         }
  14673.         if (mode == 'I') // Insert or Add.
  14674.         {
  14675.             // Note that ListView_InsertItem() will append vs. insert if the index is too large, in which case
  14676.             // it returns the items new index (which will be the last item in the list unless the control has
  14677.             // auto-sort style).
  14678.             // Below uses +1 to convert from zero-based to 1-based.  This also converts a failure result of -1 to 0.
  14679.             if (   !(aResultToken.value_int64 = ListView_InsertItem(control.hwnd, &lvi) + 1)   )
  14680.                 return; // Since item can't be inserted, no reason to try attaching any subitems to it.
  14681.             // Update iItem with the actual index assigned to the item, which might be different than the
  14682.             // specified index if the control has an auto-sort style in effect.  This new iItem value
  14683.             // is used for ListView_SetCheckState() and for the attaching of any subitems to this item.
  14684.             lvi_sub.iItem = (int)aResultToken.value_int64 - 1;  // -1 to convert back to zero-based.
  14685.             // For add/insert (but not modify), testing shows that checkmark must be added only after
  14686.             // the item has been inserted rather than provided in the lvi.state/stateMask fields.
  14687.             // MSDN confirms this by saying "When an item is added with [LVS_EX_CHECKBOXES],
  14688.             // it will always be set to the unchecked state [ignoring any value placed in bits
  14689.             // 12 through 15 of the state member]."
  14690.             if (is_checked)
  14691.                 ListView_SetCheckState(control.hwnd, lvi_sub.iItem, TRUE); // TRUE = Check the row's checkbox.
  14692.                 // Note that 95/NT4 systems that lack comctl32.dll 4.70+ distributed with MSIE 3.x
  14693.                 // do not support LVS_EX_CHECKBOXES, so the above will have no effect for them.
  14694.         }
  14695.         else // Modify.
  14696.         {
  14697.             // Rather than trying to detect if anything was actually changed, this is called
  14698.             // unconditionally to simplify the code (ListView_SetItem() is probably very fast if it
  14699.             // discovers that lvi.mask==LVIF_STATE and lvi.stateMask==0).
  14700.             // By design (to help catch script bugs), a failure here does not revert to append mode.
  14701.             if (!ListView_SetItem(control.hwnd, &lvi)) // Returns TRUE/FALSE.
  14702.                 aResultToken.value_int64 = 0; // Indicate partial failure, but attempt to continue in case it failed for reason other than "row doesn't exist".
  14703.             lvi_sub.iItem = lvi.iItem; // In preparation for modifying any subitems that need it.
  14704.             if (ensure_visible) // Seems best to do this one prior to "select" below.
  14705.                 SendMessage(control.hwnd, LVM_ENSUREVISIBLE, lvi.iItem, FALSE); // PartialOK==FALSE is somewhat arbitrary.
  14706.         }
  14707.  
  14708.         // For each remainining parameter, assign its text to a subitem.
  14709.         // Testing shows that if the control has too few columns for all of the fields/parameters
  14710.         // present, the ones at the end are automatically ignored: they do not consume memory nor
  14711.         // do they signficantly impact performance (at least on Windows XP).  For this reason, there
  14712.         // is no code above the for-loop above to reduce aParamCount if it's "too large" because
  14713.         // it might reduce flexibility (in case future/past OSes allow non-existent columns to be
  14714.         // populated, or in case current OSes allow the contents of recently removed columns to be modified).
  14715.         for (lvi_sub.iSubItem = (col_start_index > 1) ? col_start_index : 1 // Start at the first subitem unless we were told to start at or after the third column.
  14716.             // "i" starts at 2 (the third parameter) unless col_start_index is greater than 0, in which case
  14717.             // it starts at 1 (the second parameter) because that parameter has not yet been assigned to anything:
  14718.             , i = 2 - (col_start_index > 0)
  14719.             ; i < aParamCount
  14720.             ; ++i, ++lvi_sub.iSubItem)
  14721.             if (lvi_sub.pszText = ExprTokenToString(*aParam[i], buf)) // Done every time through the outer loop since it's not high-overhead, and for code simplicity.
  14722.                 if (!ListView_SetItem(control.hwnd, &lvi_sub) && mode != 'I') // Relies on short-circuit. Seems best to avoid loss of item's index in insert mode, since failure here should be rare.
  14723.                     aResultToken.value_int64 = 0; // Indicate partial failure, but attempt to continue in case it failed for reason other than "row doesn't exist".
  14724.             // else not an operand, but it's simplest just to try to continue.
  14725.     } // outer for()
  14726.  
  14727.     // When the control has no rows, work around the fact that LVM_SETITEMCOUNT delivers less than 20%
  14728.     // of its full benefit unless done after the first row is added (at least on XP SP1).  A non-zero
  14729.     // row_count_hint tells us that this message should be sent after the row has been inserted/appended:
  14730.     if (control.union_lv_attrib->row_count_hint > 0 && mode == 'I')
  14731.     {
  14732.         SendMessage(control.hwnd, LVM_SETITEMCOUNT, control.union_lv_attrib->row_count_hint, 0); // Last parameter should be 0 for LVS_OWNERDATA (verified if you look at the definition of ListView_SetItemCount macro).
  14733.         control.union_lv_attrib->row_count_hint = 0; // Reset so that it only gets set once per request.
  14734.     }
  14735. }
  14736.  
  14737.  
  14738.  
  14739. void BIF_LV_Delete(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  14740. // Returns: 1 on success and 0 on failure.
  14741. // Parameters:
  14742. // 1: Row index (one-based when it comes in).
  14743. {
  14744.     aResultToken.value_int64 = 0; // Set default return value.
  14745.     // Above sets default result in case of early return.  For code reduction, a zero is returned for all
  14746.     // the following conditions:
  14747.     // Window doesn't exist.
  14748.     // Control doesn't exist (i.e. no ListView in window).
  14749.     // And others as shown below.
  14750.  
  14751.     if (!g_gui[g.GuiDefaultWindowIndex])
  14752.         return;
  14753.     GuiType &gui = *g_gui[g.GuiDefaultWindowIndex]; // Always operate on thread's default window to simplify the syntax.
  14754.     if (!gui.mCurrentListView)
  14755.         return;
  14756.     HWND control_hwnd = gui.mCurrentListView->hwnd;
  14757.  
  14758.     if (aParamCount < 1)
  14759.     {
  14760.         aResultToken.value_int64 = SendMessage(control_hwnd, LVM_DELETEALLITEMS, 0, 0); // Returns TRUE/FALSE.
  14761.         return;
  14762.     }
  14763.  
  14764.     // Since above didn't return, there is a first paramter present.
  14765.     int index = (int)ExprTokenToInt64(*aParam[0]) - 1; // -1 to convert to zero-based.
  14766.     if (index > -1)
  14767.         aResultToken.value_int64 = SendMessage(control_hwnd, LVM_DELETEITEM, index, 0); // Returns TRUE/FALSE.
  14768.     //else even if index==0, for safety, it seems not to do a delete-all.
  14769. }
  14770.  
  14771.  
  14772.  
  14773. void BIF_LV_InsertModifyDeleteCol(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  14774. // Returns: 1 on success and 0 on failure.
  14775. // Parameters:
  14776. // 1: Column index (one-based when it comes in).
  14777. // 2: String of options
  14778. // 3: New text of column
  14779. // There are also some special modes when only zero or one parameter is present, see below.
  14780. {
  14781.     char mode = toupper(aResultToken.marker[3]); // Union's marker initially contains the function name. LV_[I]nsertCol.
  14782.     char *buf = aResultToken.buf; // Must be saved early since below overwrites the union (better maintainability too).
  14783.     aResultToken.value_int64 = 0; // Set default return value. Must be done only after consulting marker above.
  14784.     // Above sets default result in case of early return.  For code reduction, a zero is returned for all
  14785.     // the following conditions:
  14786.     // Window doesn't exist.
  14787.     // Control doesn't exist (i.e. no ListView in window).
  14788.     // Column not found in ListView.
  14789.  
  14790.     if (!g_gui[g.GuiDefaultWindowIndex])
  14791.         return;
  14792.     GuiType &gui = *g_gui[g.GuiDefaultWindowIndex]; // Always operate on thread's default window to simplify the syntax.
  14793.     if (!gui.mCurrentListView)
  14794.         return;
  14795.     GuiControlType &control = *gui.mCurrentListView;
  14796.     lv_attrib_type &lv_attrib = *control.union_lv_attrib;
  14797.  
  14798.     int index;
  14799.     if (aParamCount > 0)
  14800.         index = (int)ExprTokenToInt64(*aParam[0]) - 1; // -1 to convert to zero-based.
  14801.     else // Zero parameters.  Load-time validation has ensured that the 'D' (delete) mode cannot have zero params.
  14802.     {
  14803.         if (mode == 'M')
  14804.         {
  14805.             if (GuiType::ControlGetListViewMode(control.hwnd) != LVS_REPORT)
  14806.                 return; // And leave aResultToken.value_int64 at 0 to indicate failure.
  14807.             // Otherwise:
  14808.             aResultToken.value_int64 = 1; // Always successful (for consistency), regardless of what happens below.
  14809.             // v1.0.36.03: Don't attempt to auto-size the columns while the view is not report-view because
  14810.             // that causes any subsequent switch to the "list" view to be corrupted (invisible icons and items):
  14811.             for (int i = 0; ; ++i) // Don't limit it to lv_attrib.col_count in case script added extra columns via direct API calls.
  14812.                 if (!ListView_SetColumnWidth(control.hwnd, i, LVSCW_AUTOSIZE)) // Failure means last column has already been processed.
  14813.                     break; // Break vs. return in case the loop has zero iterations due to zero columns (not currently possible, but helps maintainability).
  14814.             return;
  14815.         }
  14816.         // Since above didn't return, mode must be 'I' (insert).
  14817.         index = lv_attrib.col_count; // When no insertion index was specified, append to the end of the list.
  14818.     }
  14819.  
  14820.     // Do this prior to checking if index is in bounds so that it can support columns beyond LV_MAX_COLUMNS:
  14821.     if (mode == 'D') // Delete a column.  In this mode, index parameter was made mandatory via load-time validation.
  14822.     {
  14823.         if (aResultToken.value_int64 = ListView_DeleteColumn(control.hwnd, index))  // Returns TRUE/FALSE.
  14824.         {
  14825.             // It's important to note that when the user slides columns around via drag and drop, the
  14826.             // column index as seen by the script is not changed.  This is fortunate because otherwise,
  14827.             // the lv_attrib.col array would get out of sync with the column indices.  Testing shows that
  14828.             // all of the following operations respect the original column index, regardless of where the
  14829.             // user may have moved the column physically: InsertCol, DeleteCol, ModifyCol.  Insert and Delete
  14830.             // shifts the indices of those columns that *originally* lay to the right of the affected column.
  14831.             if (lv_attrib.col_count > 0) // Avoid going negative, which would otherwise happen if script previously added columns by calling the API directly.
  14832.                 --lv_attrib.col_count; // Must be done prior to the below.
  14833.             if (index < lv_attrib.col_count) // When a column other than the last was removed, adjust the array so that it stays in sync with actual columns.
  14834.                 MoveMemory(lv_attrib.col+index, lv_attrib.col+index+1, sizeof(lv_col_type)*(lv_attrib.col_count-index));
  14835.         }
  14836.         return;
  14837.     }
  14838.     // Do this prior to checking if index is in bounds so that it can support columns beyond LV_MAX_COLUMNS:
  14839.     if (mode == 'M' && aParamCount < 2) // A single parameter is a special modify-mode to auto-size that column.
  14840.     {
  14841.         // v1.0.36.03: Don't attempt to auto-size the columns while the view is not report-view because
  14842.         // that causes any subsequent switch to the "list" view to be corrupted (invisible icons and items):
  14843.         if (GuiType::ControlGetListViewMode(control.hwnd) == LVS_REPORT)
  14844.             aResultToken.value_int64 = ListView_SetColumnWidth(control.hwnd, index, LVSCW_AUTOSIZE);
  14845.         //else leave aResultToken.value_int64 set to 0.
  14846.         return;
  14847.     }
  14848.     if (mode == 'I')
  14849.     {
  14850.         if (lv_attrib.col_count >= LV_MAX_COLUMNS) // No room to insert or append.
  14851.             return;
  14852.         if (index >= lv_attrib.col_count) // For convenience, fall back to "append" when index too large.
  14853.             index = lv_attrib.col_count;
  14854.     }
  14855.     //else do nothing so that modification and deletion of columns that were added via script's
  14856.     // direct calls to the API can sort-of work (it's documented in the help file that it's not supported,
  14857.     // since col-attrib array can get out of sync with actual columns that way).
  14858.  
  14859.     if (index < 0 || index >= LV_MAX_COLUMNS) // For simplicity, do nothing else if index out of bounds.
  14860.         return; // Avoid array under/overflow below.
  14861.  
  14862.     // In addition to other reasons, must convert any numeric value to a string so that an isolated width is
  14863.     // recognized, e.g. LV_SetCol(1, old_width + 10):
  14864.     char *options = (aParamCount > 1) ? ExprTokenToString(*aParam[1], buf) : "";
  14865.  
  14866.     // It's done the following way so that when in insert-mode, if the column fails to be inserted, don't
  14867.     // have to remove the inserted array element from the lv_attrib.col array:
  14868.     lv_col_type temp_col = {0}; // Init unconditionally even though only needed for mode=='I'.
  14869.     lv_col_type &col = (mode == 'I') ? temp_col : lv_attrib.col[index]; // Done only after index has been confirmed in-bounds.
  14870.  
  14871.     LVCOLUMN lvc;
  14872.     lvc.mask = LVCF_FMT;
  14873.     if (mode == 'M') // Fetch the current format so that it's possible to leave parts of it unaltered.
  14874.         ListView_GetColumn(control.hwnd, index, &lvc);
  14875.     else // Mode is "insert".
  14876.         lvc.fmt = 0;
  14877.  
  14878.     // Init defaults prior to parsing options:
  14879.     bool sort_now = false;
  14880.     int do_auto_size = (mode == 'I') ? LVSCW_AUTOSIZE_USEHEADER : 0;  // Default to auto-size for new columns.
  14881.     char sort_now_direction = 'A'; // Ascending.
  14882.     int new_justify = lvc.fmt & LVCFMT_JUSTIFYMASK; // Simplifies the handling of the justification bitfield.
  14883.     //lvc.iSubItem = 0; // Not necessary if the LVCF_SUBITEM mask-bit is absent.
  14884.  
  14885.     // Parse list of space-delimited options:
  14886.     char *next_option, *option_end, orig_char;
  14887.     bool adding; // Whether this option is beeing added (+) or removed (-).
  14888.  
  14889.     for (next_option = options; *next_option; next_option = omit_leading_whitespace(option_end))
  14890.     {
  14891.         if (*next_option == '-')
  14892.         {
  14893.             adding = false;
  14894.             // omit_leading_whitespace() is not called, which enforces the fact that the option word must
  14895.             // immediately follow the +/- sign.  This is done to allow the flexibility to have options
  14896.             // omit the plus/minus sign, and also to reserve more flexibility for future option formats.
  14897.             ++next_option;  // Point it to the option word itself.
  14898.         }
  14899.         else
  14900.         {
  14901.             // Assume option is being added in the absence of either sign.  However, when we were
  14902.             // called by GuiControl(), the first option in the list must begin with +/- otherwise the cmd
  14903.             // would never have been properly detected as GUICONTROL_CMD_OPTIONS in the first place.
  14904.             adding = true;
  14905.             if (*next_option == '+')
  14906.                 ++next_option;  // Point it to the option word itself.
  14907.             //else do not increment, under the assumption that the plus has been omitted from a valid
  14908.             // option word and is thus an implicit plus.
  14909.         }
  14910.  
  14911.         if (!*next_option) // In case the entire option string ends in a naked + or -.
  14912.             break;
  14913.         // Find the end of this option item:
  14914.         if (   !(option_end = StrChrAny(next_option, " \t"))   )  // Space or tab.
  14915.             option_end = next_option + strlen(next_option); // Set to position of zero terminator instead.
  14916.         if (option_end == next_option)
  14917.             continue; // i.e. the string contains a + or - with a space or tab after it, which is intentionally ignored.
  14918.  
  14919.         // Temporarily terminate to help eliminate ambiguity for words contained inside other words,
  14920.         // such as "Checked" inside of "CheckedGray":
  14921.         orig_char = *option_end;
  14922.         *option_end = '\0';
  14923.  
  14924.         // For simplicity, the value of "adding" is ignored for this and the other number/alignment options.
  14925.         if (!stricmp(next_option, "Integer"))
  14926.         {
  14927.             // For simplicity, changing the col.type dynamically (since it's so rarely needed)
  14928.             // does not try to set up col.is_now_sorted_ascending so that the next click on the column
  14929.             // puts it into default starting order (which is ascending unless the Desc flag was originally
  14930.             // present).
  14931.             col.type = LV_COL_INTEGER;
  14932.             new_justify = LVCFMT_RIGHT;
  14933.         }
  14934.         else if (!stricmp(next_option, "Float"))
  14935.         {
  14936.             col.type = LV_COL_FLOAT;
  14937.             new_justify = LVCFMT_RIGHT;
  14938.         }
  14939.         else if (!stricmp(next_option, "Text")) // Seems more approp. name than "Str" or "String"
  14940.             // Since "Text" is so general, it seems to leave existing alignment (Center/Right) as it is.
  14941.             col.type = LV_COL_TEXT;
  14942.  
  14943.         // The following can exist by themselves or in conjunction with the above.  They can also occur
  14944.         // *after* one of the above words so that alignment can be used to override the default for the type;
  14945.         // e.g. "Integer Left" to have left-aligned integers.
  14946.         else if (!stricmp(next_option, "Right"))
  14947.             new_justify = adding ? LVCFMT_RIGHT : LVCFMT_LEFT;
  14948.         else if (!stricmp(next_option, "Center"))
  14949.             new_justify = adding ? LVCFMT_CENTER : LVCFMT_LEFT;
  14950.         else if (!stricmp(next_option, "Left")) // Supported so that existing right/center column can be changed back to left.
  14951.             new_justify = LVCFMT_LEFT; // The value of "adding" seems inconsequential so is ignored.
  14952.  
  14953.         else if (!stricmp(next_option, "Uni")) // Unidirectional sort (clicking the column will not invert to the opposite direction).
  14954.             col.unidirectional = adding;
  14955.         else if (!stricmp(next_option, "Desc")) // Make descending order the default order (applies to uni and first click of col for non-uni).
  14956.             col.prefer_descending = adding; // So that the next click will toggle to the opposite direction.
  14957.         else if (!strnicmp(next_option, "Case", 4))
  14958.         {
  14959.             if (adding)
  14960.                 col.case_sensitive = !stricmp(next_option + 4, "Locale") ? SCS_INSENSITIVE_LOCALE : SCS_SENSITIVE;
  14961.             else
  14962.                 col.case_sensitive = SCS_INSENSITIVE;
  14963.         }
  14964.         else if (!stricmp(next_option, "Logical")) // v1.0.44.12: Supports StrCmpLogicalW() method of sorting.
  14965.             col.case_sensitive = SCS_INSENSITIVE_LOGICAL;
  14966.  
  14967.         else if (!strnicmp(next_option, "Sort", 4)) // This is done as an option vs. LV_SortCol/LV_Sort so that the column's options can be changed simultaneously with a "sort now" to refresh.
  14968.         {
  14969.             // Defer the sort until after all options have been parsed and applied.
  14970.             sort_now = true;
  14971.             if (!stricmp(next_option + 4, "Desc"))
  14972.                 sort_now_direction = 'D'; // Descending.
  14973.         }
  14974.         else if (!stricmp(next_option, "NoSort")) // Called "NoSort" so that there's a way to enable and disable the setting via +/-.
  14975.             col.sort_disabled = adding;
  14976.  
  14977.         else if (!strnicmp(next_option, "Auto", 4)) // No separate failure result is reported for this item.
  14978.             // In case the mode is "insert", defer auto-width of column until col exists.
  14979.             do_auto_size = stricmp(next_option + 4, "Hdr") ? LVSCW_AUTOSIZE : LVSCW_AUTOSIZE_USEHEADER;
  14980.  
  14981.         else if (!strnicmp(next_option, "Icon", 4))
  14982.         {
  14983.             next_option += 4;
  14984.             if (!stricmp(next_option, "Right"))
  14985.             {
  14986.                 if (adding)
  14987.                     lvc.fmt |= LVCFMT_BITMAP_ON_RIGHT;
  14988.                 else
  14989.                     lvc.fmt &= ~LVCFMT_BITMAP_ON_RIGHT;
  14990.             }
  14991.             else // Assume its an icon number or the removal of the icon via -Icon.
  14992.             {
  14993.                 if (adding)
  14994.                 {
  14995.                     lvc.mask |= LVCF_IMAGE;
  14996.                     lvc.fmt |= LVCFMT_IMAGE; // Flag this column as displaying an image.
  14997.                     lvc.iImage = ATOI(next_option) - 1; // -1 to convert to zero based.
  14998.                 }
  14999.                 else
  15000.                     lvc.fmt &= ~LVCFMT_IMAGE; // Flag this column as NOT displaying an image.
  15001.             }
  15002.         }
  15003.  
  15004.         else // Handle things that are more general than the above, such as single letter options and pure numbers.
  15005.         {
  15006.             // Width does not have a W prefix to permit a naked expression to be used as the entirely of
  15007.             // options.  For example: LV_SetCol(1, old_width + 10)
  15008.             // v1.0.37: Fixed to allow floating point (although ATOI below will convert it to integer).
  15009.             if (IsPureNumeric(next_option, true, false, true)) // Above has already verified that *next_option can't be whitespace.
  15010.             {
  15011.                 do_auto_size = 0; // Turn off any auto-sizing that may have been put into effect by default (such as for insertion).
  15012.                 lvc.mask |= LVCF_WIDTH;
  15013.                 lvc.cx = ATOI(next_option);
  15014.             }
  15015.         }
  15016.  
  15017.         // If the item was not handled by the above, ignore it because it is unknown.
  15018.         *option_end = orig_char; // Undo the temporary termination because the caller needs aOptions to be unaltered.
  15019.     }
  15020.  
  15021.     // Apply any changed justification/alignment to the fmt bit field:
  15022.     lvc.fmt = (lvc.fmt & ~LVCFMT_JUSTIFYMASK) | new_justify;
  15023.  
  15024.     if (aParamCount > 2) // Parameter #3 (text) is present.
  15025.     {
  15026.         lvc.pszText = ExprTokenToString(*aParam[2], buf);
  15027.         lvc.mask |= LVCF_TEXT;
  15028.     }
  15029.  
  15030.     if (mode == 'M') // Modify vs. Insert (Delete was already returned from, higher above).
  15031.         // For code simplicity, this is called unconditionally even if nothing internal the control's column
  15032.         // needs updating.  This seems justified given how rarely columns are modified.
  15033.         aResultToken.value_int64 = ListView_SetColumn(control.hwnd, index, &lvc); // Returns TRUE/FALSE.
  15034.     else // Insert
  15035.     {
  15036.         // It's important to note that when the user slides columns around via drag and drop, the
  15037.         // column index as seen by the script is not changed.  This is fortunate because otherwise,
  15038.         // the lv_attrib.col array would get out of sync with the column indices.  Testing shows that
  15039.         // all of the following operations respect the original column index, regardless of where the
  15040.         // user may have moved the column physically: InsertCol, DeleteCol, ModifyCol.  Insert and Delete
  15041.         // shifts the indices of those columns that *originally* lay to the right of the affected column.
  15042.         // Doesn't seem to do anything -- not even with respect to inserting a new first column with it's
  15043.         // unusual behavior of inheriting the previously column's contents -- so it's disabled for now.
  15044.         // Testing shows that it also does not seem to cause a new column to inherit the indicated subitem's
  15045.         // text, even when iSubItem is set to index + 1 vs. index:
  15046.         //lvc.mask |= LVCF_SUBITEM;
  15047.         //lvc.iSubItem = index;
  15048.         // Testing shows that the following serve to set the column's physical/display position in the
  15049.         // heading to iOrder without affecting the specified index.  This concept is very similar to
  15050.         // when the user drags and drops a column heading to a new position: it's index doesn't change,
  15051.         // only it's displayed position:
  15052.         //lvc.mask |= LVCF_ORDER;
  15053.         //lvc.iOrder = index + 1;
  15054.         if (   !(aResultToken.value_int64 = ListView_InsertColumn(control.hwnd, index, &lvc) + 1)   ) // +1 to convert the new index to 1-based.
  15055.             return; // Since column could not be inserted, return so that below, sort-now, etc. are not done.
  15056.         index = (int)aResultToken.value_int64 - 1; // Update in case some other index was assigned. -1 to convert back to zero-based.
  15057.         if (index < lv_attrib.col_count) // Since col is not being appended to the end, make room in the array to insert this column.
  15058.             MoveMemory(lv_attrib.col+index+1, lv_attrib.col+index, sizeof(lv_col_type)*(lv_attrib.col_count-index));
  15059.             // Above: Shift columns to the right by one.
  15060.         lv_attrib.col[index] = col; // Copy temp struct's members to the correct element in the array.
  15061.         // The above is done even when index==0 because "col" may contain attributes set via the Options
  15062.         // parameter.  Therefore, for code simplicity and rarity of real-world need, no attempt is made
  15063.         // to make the following idea work:
  15064.         // When index==0, retain the existing attributes due to the unique behavior of inserting a new first
  15065.         // column: The new first column inherit's the old column's values (fields), so it seems best to also have it
  15066.         // inherit the old column's attributs.
  15067.         ++lv_attrib.col_count; // New column successfully added.  Must be done only after the MoveMemory() above.
  15068.     }
  15069.  
  15070.     // Auto-size is done only at this late a stage, in case column was just created above.
  15071.     // Note that ListView_SetColumn() apparently does not support LVSCW_AUTOSIZE_USEHEADER for it's "cx" member.
  15072.     if (do_auto_size && GuiType::ControlGetListViewMode(control.hwnd) == LVS_REPORT)
  15073.         ListView_SetColumnWidth(control.hwnd, index, do_auto_size); // aResultToken.value_int64 was previous set to the more important result above.
  15074.     //else v1.0.36.03: Don't attempt to auto-size the columns while the view is not report-view because
  15075.     // that causes any subsequent switch to the "list" view to be corrupted (invisible icons and items).
  15076.  
  15077.     if (sort_now)
  15078.         GuiType::LV_Sort(control, index, false, sort_now_direction);
  15079. }
  15080.  
  15081.  
  15082.  
  15083. void BIF_LV_SetImageList(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  15084. // Returns (MSDN): "handle to the image list previously associated with the control if successful; NULL otherwise."
  15085. // Parameters:
  15086. // 1: HIMAGELIST obtained from somewhere such as IL_Create().
  15087. // 2: Optional: Type of list.
  15088. {
  15089.     aResultToken.value_int64 = 0; // Set default return value.
  15090.     // Above sets default result in case of early return.  For code reduction, a zero is returned for all
  15091.     // the following conditions:
  15092.     // Window doesn't exist.
  15093.     // Control doesn't exist (i.e. no ListView in window).
  15094.     // Column not found in ListView.
  15095.  
  15096.     if (!g_gui[g.GuiDefaultWindowIndex])
  15097.         return;
  15098.     GuiType &gui = *g_gui[g.GuiDefaultWindowIndex]; // Always operate on thread's default window to simplify the syntax.
  15099.     if (!gui.mCurrentListView)
  15100.         return;
  15101.     // Caller has ensured that there is at least one incoming parameter:
  15102.     HIMAGELIST himl = (HIMAGELIST)ExprTokenToInt64(*aParam[0]);
  15103.     int list_type;
  15104.     if (aParamCount > 1)
  15105.         list_type = (int)ExprTokenToInt64(*aParam[1]);
  15106.     else // Auto-detect large vs. small icons based on the actual icon size in the image list.
  15107.     {
  15108.         int cx, cy;
  15109.         ImageList_GetIconSize(himl, &cx, &cy);
  15110.         list_type = (cx > GetSystemMetrics(SM_CXSMICON)) ? LVSIL_NORMAL : LVSIL_SMALL;
  15111.     }
  15112.     aResultToken.value_int64 = (__int64)ListView_SetImageList(gui.mCurrentListView->hwnd, himl, list_type);
  15113. }
  15114.  
  15115.  
  15116.  
  15117. void BIF_TV_AddModifyDelete(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  15118. // TV_Add():
  15119. // Returns the HTREEITEM of the item on success, zero on failure.
  15120. // Parameters:
  15121. //    1: Text/name of item.
  15122. //    2: Parent of item.
  15123. //    3: Options.
  15124. // TV_Modify():
  15125. // Returns the HTREEITEM of the item on success (to allow nested calls in script, zero on failure or partial failure.
  15126. // Parameters:
  15127. //    1: ID of item to modify.
  15128. //    2: Options.
  15129. //    3: New name.
  15130. // Parameters for TV_Delete():
  15131. //    1: ID of item to delete (if omitted, all items are deleted).
  15132. {
  15133.     char mode = toupper(aResultToken.marker[3]); // Union's marker initially contains the function name. e.g. TV_[A]dd.
  15134.     char *buf = aResultToken.buf; // Must be saved early since below overwrites the union (better maintainability too).
  15135.     aResultToken.value_int64 = 0; // Set default return value. Must be done only after consulting marker above.
  15136.     // Above sets default result in case of early return.  For code reduction, a zero is returned for all
  15137.     // the following conditions:
  15138.     // Window doesn't exist.
  15139.     // Control doesn't exist (i.e. no TreeView in window).
  15140.     // And others as shown below.
  15141.  
  15142.     if (!g_gui[g.GuiDefaultWindowIndex])
  15143.         return;
  15144.     GuiType &gui = *g_gui[g.GuiDefaultWindowIndex]; // Always operate on thread's default window to simplify the syntax.
  15145.     if (!gui.mCurrentTreeView)
  15146.         return;
  15147.     GuiControlType &control = *gui.mCurrentTreeView;
  15148.  
  15149.     if (mode == 'D') // TV_Delete
  15150.     {
  15151.         // If param #1 is present but is zero, for safety it seems not to do a delete-all (in case a
  15152.         // script bug is so rare that it is never caught until the script is distributed).  Another reason
  15153.         // is that a script might do something like TV_Delete(TV_GetSelection()), which would be desired
  15154.         // to fail not delete-all if there's ever any way for there to be no selection.
  15155.         aResultToken.value_int64 = SendMessage(control.hwnd, TVM_DELETEITEM, 0
  15156.             , aParamCount < 1 ? NULL : (LPARAM)ExprTokenToInt64(*aParam[0]));
  15157.         return;
  15158.     }
  15159.  
  15160.     // Since above didn't return, this is TV_Add() or TV_Modify().
  15161.     TVINSERTSTRUCT tvi; // It contains a TVITEMEX, which is okay even if MSIE pre-4.0 on Win95/NT because those OSes will simply never access the new/bottommost item in the struct.
  15162.     bool add_mode = (mode == 'A'); // For readability & maint.
  15163.  
  15164.     char *options;
  15165.     if (add_mode) // TV_Add()
  15166.     {
  15167.         tvi.hParent = (aParamCount > 1) ? (HTREEITEM)ExprTokenToInt64(*aParam[1]) : NULL;
  15168.         tvi.hInsertAfter = TVI_LAST; // i.e. default is to insert the new item underneath the bottomost sibling.
  15169.         options = (aParamCount > 2) ? ExprTokenToString(*aParam[2], buf) : "";
  15170.     }
  15171.     else // TV_Modify()
  15172.     {
  15173.         // NOTE: Must allow hitem==0 for TV_Modify, at least for the Sort option, because otherwise there would
  15174.         // be no way to sort the root-level items.
  15175.         tvi.item.hItem = (HTREEITEM)ExprTokenToInt64(*aParam[0]); // Load-time validation has ensured there is a first parameter for TV_Modify().
  15176.         // For modify-mode, set default return value to be "success" from this point forward.  Note that
  15177.         // in the case of sorting the root-level items, this will set it to zero, but since that almost
  15178.         // always suceeds and the script rarely cares whether it succeeds or not, adding code size for that
  15179.         // doesn't seem worth it:
  15180.         aResultToken.value_int64 = (size_t)tvi.item.hItem;
  15181.         if (aParamCount < 2) // In one-parameter mode, simply select the item.
  15182.         {
  15183.             if (!TreeView_SelectItem(control.hwnd, tvi.item.hItem))
  15184.                 aResultToken.value_int64 = 0; // Override the HTREEITEM default value set above.
  15185.             return;
  15186.         }
  15187.         // Otherwise, there's a second parameter (even if it's 0 or "").
  15188.         options = ExprTokenToString(*aParam[1], buf);
  15189.     }
  15190.  
  15191.     // Set defaults prior to options-parsing, to cover all omitted defaults:
  15192.     tvi.item.mask = TVIF_STATE; // TVIF_STATE: The state and stateMask members are valid (all other members are ignored).
  15193.     tvi.item.stateMask = 0; // All bits in "state" below are ignored unless the corresponding bit is present here in the mask.
  15194.     tvi.item.state = 0;
  15195.     // It seems tvi.item.cChildren is typically maintained by the control, though one exception is I_CHILDRENCALLBACK
  15196.     // and TVN_GETDISPINFO as mentioned at MSDN.
  15197.  
  15198.     DWORD select_flag = 0;
  15199.     bool ensure_visible = false, ensure_visible_first = false;
  15200.  
  15201.     // Parse list of space-delimited options:
  15202.     char *next_option, *option_end, orig_char;
  15203.     bool adding; // Whether this option is beeing added (+) or removed (-).
  15204.  
  15205.     for (next_option = options; *next_option; next_option = omit_leading_whitespace(option_end))
  15206.     {
  15207.         if (*next_option == '-')
  15208.         {
  15209.             adding = false;
  15210.             // omit_leading_whitespace() is not called, which enforces the fact that the option word must
  15211.             // immediately follow the +/- sign.  This is done to allow the flexibility to have options
  15212.             // omit the plus/minus sign, and also to reserve more flexibility for future option formats.
  15213.             ++next_option;  // Point it to the option word itself.
  15214.         }
  15215.         else
  15216.         {
  15217.             // Assume option is being added in the absence of either sign.  However, when we were
  15218.             // called by GuiControl(), the first option in the list must begin with +/- otherwise the cmd
  15219.             // would never have been properly detected as GUICONTROL_CMD_OPTIONS in the first place.
  15220.             adding = true;
  15221.             if (*next_option == '+')
  15222.                 ++next_option;  // Point it to the option word itself.
  15223.             //else do not increment, under the assumption that the plus has been omitted from a valid
  15224.             // option word and is thus an implicit plus.
  15225.         }
  15226.  
  15227.         if (!*next_option) // In case the entire option string ends in a naked + or -.
  15228.             break;
  15229.         // Find the end of this option item:
  15230.         if (   !(option_end = StrChrAny(next_option, " \t"))   )  // Space or tab.
  15231.             option_end = next_option + strlen(next_option); // Set to position of zero terminator instead.
  15232.         if (option_end == next_option)
  15233.             continue; // i.e. the string contains a + or - with a space or tab after it, which is intentionally ignored.
  15234.  
  15235.         // Temporarily terminate to help eliminate ambiguity for words contained inside other words,
  15236.         // such as "Checked" inside of "CheckedGray":
  15237.         orig_char = *option_end;
  15238.         *option_end = '\0';
  15239.  
  15240.         if (!stricmp(next_option, "Select")) // Could further allow "ed" suffix by checking for that inside, but "Selected" is getting long so it doesn't seem something many would want to use.
  15241.         {
  15242.             // Selection of an item apparently needs to be done via message for the control to update itself
  15243.             // properly.  Otherwise, single-select isn't enforced via de-selecitng previous item and the newly
  15244.             // selected item isn't revealed/shown.  There may be other side-effects.
  15245.             if (adding)
  15246.                 select_flag = TVGN_CARET;
  15247.             //else since "de-select" is not a supported action, no need to support "-Select".
  15248.             // Furthermore, since a TreeView is by its nature has only one item selected at a time, it seems
  15249.             // unnecessary to support Select%VarContainingOneOrZero%.  This is because it seems easier for a
  15250.             // script to simply load the Tree then select the desired item afterward.
  15251.         }
  15252.         else if (!strnicmp(next_option, "Vis", 3))
  15253.         {
  15254.             // Since this option much more typically used with TV_Modify than TV_Add, the technique of
  15255.             // Vis%VarContainingOneOrZero% isn't supported, to reduce code size.
  15256.             next_option += 3;
  15257.             if (!stricmp(next_option, "First")) // VisFirst
  15258.                 ensure_visible_first = adding;
  15259.             else if (!*next_option)
  15260.                 ensure_visible = adding;
  15261.         }
  15262.         else if (!stricmp(next_option, "Bold"))
  15263.         {
  15264.             // Bold%VarContainingOneOrZero isn't supported because due to rarity.  There might someday
  15265.             // be a ternary operator to make such things easier anyway.
  15266.             tvi.item.stateMask |= TVIS_BOLD;
  15267.             if (adding)
  15268.                 tvi.item.state |= TVIS_BOLD;
  15269.             //else removing, so the fact that this TVIS flag has just been added to the stateMask above
  15270.             // but is absent from item.state should remove this attribute from the item.
  15271.         }
  15272.         else if (!strnicmp(next_option, "Expand", 6))
  15273.         {
  15274.             next_option += 6;
  15275.             if (*next_option && !ATOI(next_option)) // If it's Expand0, invert the mode to become "collapse".
  15276.                 adding = !adding;
  15277.             if (add_mode)
  15278.             {
  15279.                 if (adding)
  15280.                 {
  15281.                     // Don't expand via msg because it won't work: since the item is being newly added
  15282.                     // now, by definition it doesn't have any children, and testing shows that sending
  15283.                     // the expand message has no effect, but setting the state bit does:
  15284.                     tvi.item.stateMask |= TVIS_EXPANDED;
  15285.                     tvi.item.state |= TVIS_EXPANDED;
  15286.                     // Since the script is deliberately expanding the item, it seems best not to send the
  15287.                     // TVN_ITEMEXPANDING/-ED messages because:
  15288.                     // 1) Sending TVN_ITEMEXPANDED without first sending a TVN_ITEMEXPANDING message might
  15289.                     //    decrease maintainability, and possibly even produce unwanted side-effects.
  15290.                     // 2) Code size and performance (avoids generating extra message traffic).
  15291.                 }
  15292.                 //else removing, so nothing needs to be done because "collapsed" is the default state
  15293.                 // of a TV item upon creation.
  15294.             }
  15295.             else // TV_Modify(): Expand and collapse both require a message to work properly on an existing item.
  15296.                 // Strangely, this generates a notification sometimes (such as the first time) but not for subsequent
  15297.                 // expands/collapses of that same item.  Also, TVE_TOGGLE is not currently supported because it seems
  15298.                 // like it would be too rarely used.
  15299.                 if (!TreeView_Expand(control.hwnd, tvi.item.hItem, adding ? TVE_EXPAND : TVE_COLLAPSE))
  15300.                     aResultToken.value_int64 = 0; // Indicate partial failure by overriding the HTREEITEM return value set earlier.
  15301.                     // It seems that despite what MSDN says, failure is returned when collapsing and item that is
  15302.                     // already collapsed, but not when expanding an item that is already expanded.  For performance
  15303.                     // reasons and rarity of script caring, it seems best not to try to adjust/fix this.
  15304.         }
  15305.         else if (!strnicmp(next_option, "Check", 5))
  15306.         {
  15307.             // The rationale for not checking for an optional "ed" suffix here and incrementing next_option by 2
  15308.             // is that: 1) It would be inconsistent with the lack of support for "selected" (see reason above);
  15309.             // 2) Checkboxes in a ListView are fairly rarely used, so code size reduction might be more important.
  15310.             next_option += 5;
  15311.             if (*next_option && !ATOI(next_option)) // If it's Check0, invert the mode to become "unchecked".
  15312.                 adding = !adding;
  15313.             //else removing, so the fact that this TVIS flag has just been added to the stateMask above
  15314.             // but is absent from item.state should remove this attribute from the item.
  15315.             tvi.item.stateMask |= TVIS_STATEIMAGEMASK;  // Unlike ListViews, Tree checkmarks can be applied in the same step as creating a Tree item.
  15316.             tvi.item.state |= adding ? 0x2000 : 0x1000; // The #1 image is "unchecked" and the #2 is "checked".
  15317.         }
  15318.         else if (!strnicmp(next_option, "Icon", 4))
  15319.         {
  15320.             if (adding)
  15321.             {
  15322.                 // To me, having a different icon for when the item is selected seems rarely used.  After all,
  15323.                 // its obvious the item is selected because it's highlighed (unless it lacks a name?)  So this
  15324.                 // policy makes things easier for scripts that don't want to distinguish.  If ever it is needed,
  15325.                 // new options such as IconSel and IconUnsel can be added.
  15326.                 tvi.item.mask |= TVIF_IMAGE|TVIF_SELECTEDIMAGE;
  15327.                 tvi.item.iSelectedImage = tvi.item.iImage = ATOI(next_option + 4) - 1;  // -1 to convert to zero-based.
  15328.             }
  15329.             //else removal of icon currently not supported (see comment above), so do nothing in order
  15330.             // to reserve "-Icon" in case a future way can be found to do it.
  15331.         }
  15332.         else if (!stricmp(next_option, "Sort"))
  15333.         {
  15334.             if (add_mode)
  15335.                 tvi.hInsertAfter = TVI_SORT; // For simplicity, the value of "adding" is ignored.
  15336.             else
  15337.                 // Somewhat debatable, but it seems best to report failure via the return value even though
  15338.                 // failure probably only occurs when the item has no children, and the script probably
  15339.                 // doesn't often care about such failures.  It does result in the loss of the HTREEITEM return
  15340.                 // value, but even if that call is nested in another, the zero should produce no effect in most cases.
  15341.                 if (!TreeView_SortChildren(control.hwnd, tvi.item.hItem, FALSE)) // Best default seems no-recurse, since typically this is used after a user edits merely a single item.
  15342.                     aResultToken.value_int64 = 0; // Indicate partial failure by overriding the HTREEITEM return value set earlier.
  15343.         }
  15344.         else if (add_mode) // MUST BE LISTED LAST DUE TO "ELSE IF": Options valid only for TV_Add().
  15345.         {
  15346.             if (!stricmp(next_option, "First"))
  15347.                 tvi.hInsertAfter = TVI_FIRST; // For simplicity, the value of "adding" is ignored.
  15348.             else if (IsPureNumeric(next_option, false, false, false))
  15349.                 tvi.hInsertAfter = (HTREEITEM)ATOI64(next_option); // ATOI64 vs. ATOU avoids need for extra casting to avoid compiler warning.
  15350.         }
  15351.         //else some unknown option, just ignore it.
  15352.  
  15353.         // If the item was not handled by the above, ignore it because it is unknown.
  15354.         *option_end = orig_char; // Undo the temporary termination because the caller needs aOptions to be unaltered.
  15355.     }
  15356.  
  15357.     if (add_mode) // TV_Add()
  15358.     {
  15359.         tvi.item.pszText = ExprTokenToString(*aParam[0], buf);
  15360.         tvi.item.mask |= TVIF_TEXT;
  15361.         tvi.item.hItem = TreeView_InsertItem(control.hwnd, &tvi); // Update tvi.item.hItem for convenience/maint. I'ts for use in later sections because aResultToken.value_int64 is overridden to be zero for partial failure in modify-mode.
  15362.         aResultToken.value_int64 = (__int64)tvi.item.hItem; // Set return value.
  15363.     }
  15364.     else // TV_Modify()
  15365.     {
  15366.         if (aParamCount > 2) // An explicit empty string is allowed, which sets it to a blank value.  By contrast, if the param is omitted, the name is left changed.
  15367.         {
  15368.             tvi.item.pszText = ExprTokenToString(*aParam[2], buf); // Reuse buf now that options (above) is done with it.
  15369.             tvi.item.mask |= TVIF_TEXT;
  15370.         }
  15371.         //else name/text parameter has been omitted, so don't change the item's name.
  15372.         if (tvi.item.mask != LVIF_STATE || tvi.item.stateMask) // An item's property or one of the state bits needs changing.
  15373.             if (!TreeView_SetItem(control.hwnd, &tvi.itemex))
  15374.                 aResultToken.value_int64 = 0; // Indicate partial failure by overriding the HTREEITEM return value set earlier.
  15375.     }
  15376.  
  15377.     if (ensure_visible) // Seems best to do this one prior to "select" below.
  15378.         SendMessage(control.hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)tvi.item.hItem); // Return value is ignored in this case, since its definition seems a little weird.
  15379.     if (ensure_visible_first) // Seems best to do this one prior to "select" below.
  15380.         TreeView_Select(control.hwnd, tvi.item.hItem, TVGN_FIRSTVISIBLE); // Return value is also ignored due to rarity, code size, and because most people wouldn't care about a failure even if for some reason it failed.
  15381.     if (select_flag)
  15382.         if (!TreeView_Select(control.hwnd, tvi.item.hItem, select_flag) && !add_mode) // Relies on short-circuit boolean order.
  15383.             aResultToken.value_int64 = 0; // When not in add-mode, indicate partial failure by overriding the return value set earlier (add-mode should always return the new item's ID).
  15384. }
  15385.  
  15386.  
  15387.  
  15388. HTREEITEM GetNextTreeItem(HWND aTreeHwnd, HTREEITEM aItem)
  15389. // Helper function for others below.
  15390. // If aItem is NULL, caller wants topmost ROOT item returned.
  15391. // Otherwise, the next child, sibling, or parent's sibling is returned in a manner that allows the caller
  15392. // to traverse every item in the tree easily.
  15393. {
  15394.     if (!aItem)
  15395.         return TreeView_GetRoot(aTreeHwnd);
  15396.     // Otherwise, do depth-first recursion.  Must be done in the following order to allow full traversal:
  15397.     // Children first.
  15398.     // Then siblings.
  15399.     // Then parent's sibling(s).
  15400.     HTREEITEM hitem;
  15401.     if (hitem = TreeView_GetChild(aTreeHwnd, aItem))
  15402.         return hitem;
  15403.     if (hitem = TreeView_GetNextSibling(aTreeHwnd, aItem))
  15404.         return hitem;
  15405.     // The last stage is trickier than the above: parent's next sibling, or if none, its parent's parent's sibling, etc.
  15406.     for (HTREEITEM hparent = aItem;;)
  15407.     {
  15408.         if (   !(hparent = TreeView_GetParent(aTreeHwnd, hparent))   ) // No parent, so this is a root-level item.
  15409.             return NULL; // There is no next item.
  15410.         // Now it's known there is a parent.  It's not necessary to check that parent's children because that
  15411.         // would have been done by a prior iteration in the script.
  15412.         if (hitem = TreeView_GetNextSibling(aTreeHwnd, hparent))
  15413.             return hitem;
  15414.         // Otherwise, parent has no sibling, but does its parent (and so on)? Continue looping to find out.
  15415.     }
  15416. }
  15417.  
  15418.  
  15419.  
  15420. void BIF_TV_GetRelatedItem(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  15421. // TV_GetParent/Child/Selection/Next/Prev(hitem):
  15422. // The above all return the HTREEITEM (or 0 on failure).
  15423. // When TV_GetNext's second parameter is present, the search scope expands to include not just siblings,
  15424. // but also children and parents, which allows a tree to be traversed from top to bottom without the script
  15425. // having to do something fancy.
  15426. {
  15427.     char *fn_name = aResultToken.marker; // Save early for maintainability: Union's marker initially contains the function name. TV_Get[S]election.
  15428.     char *buf = aResultToken.buf; // Must be saved early since below overwrites the union (better maintainability too).
  15429.     aResultToken.value_int64 = 0; // Set default return value. Must be done only after saving marker above.
  15430.     // Above sets default result in case of early return.  For code reduction, a zero is returned for all
  15431.     // the following conditions:
  15432.     // Window doesn't exist.
  15433.     // Control doesn't exist (i.e. no TreeView in window).
  15434.     // Item not found in TreeView.
  15435.  
  15436.     if (!g_gui[g.GuiDefaultWindowIndex])
  15437.         return;
  15438.     GuiType &gui = *g_gui[g.GuiDefaultWindowIndex]; // Always operate on thread's default window to simplify the syntax.
  15439.     if (!gui.mCurrentTreeView)
  15440.         return;
  15441.     HWND control_hwnd = gui.mCurrentTreeView->hwnd;
  15442.  
  15443.     // For all built-in functions, loadtime validation has ensured that a first parameter can be
  15444.     // present only when it's the specified HTREEITEM.
  15445.     HTREEITEM hitem = (aParamCount < 1) ? NULL : (HTREEITEM)ExprTokenToInt64(*aParam[0]);
  15446.  
  15447.     if (aParamCount < 2)
  15448.     {
  15449.         WPARAM flag;
  15450.         char char7 = toupper(fn_name[7]);
  15451.         switch(toupper(fn_name[6]))
  15452.         {
  15453.         case 'S': flag = TVGN_CARET; break; // TV_GetSelection(). TVGN_CARET is focused item.
  15454.         case 'P': flag = (char7 == 'A') ? TVGN_PARENT : TVGN_PREVIOUS; break; // TV_GetParent/Prev.
  15455.         case 'N': flag = (aParamCount < 1 || !hitem) ? TVGN_ROOT : TVGN_NEXT; break; // TV_GetNext(no-parameters) yields very first item in Tree (TVGN_ROOT).
  15456.         // Above: It seems best to treat hitem==0 as "get root", even though it sacrificies some error detection,
  15457.         // because not doing so would be inconsistent with the fact that TV_GetNext(0, "Full") does get the root
  15458.         // (which needs to be retained to make script loops to traverse entire tree easier).
  15459.         case 'C':
  15460.             if (char7 == 'O') // i.e. the "CO" in TV_GetCount().
  15461.             {
  15462.                 // There's a known bug mentioned at MSDN that a TreeView might report a negative count when there
  15463.                 // are more than 32767 items in it (though of course HTREEITEM values are never negative since they're
  15464.                 // defined as unsigned pseudo-addresses)).  But apparently, that bug only applies to Visual Basic and/or
  15465.                 // older OSes, because testing shows that SendMessage(TVM_GETCOUNT) returns 32800+ when there are more
  15466.                 // than 32767 items in the tree, even without casting to unsigned.  So I'm not sure exactly what the
  15467.                 // story is with this, so for now just casting to UINT rather than something less future-proof like WORD:
  15468.                 // Older note, apparently unneeded at least on XP SP2: Cast to WORD to convert -1 through -32768 to the
  15469.                 // positive counterparts.
  15470.                 aResultToken.value_int64 = (UINT)SendMessage(control_hwnd, TVM_GETCOUNT, 0, 0);
  15471.                 return;
  15472.             }
  15473.             // Since above didn't return, it's TV_GetChild():
  15474.             flag = TVGN_CHILD;
  15475.             break;
  15476.         }
  15477.         // Apparently there's no direct call to get the topmost ancestor of an item, presumably because it's rarely
  15478.         // needed.  Therefore, no such mode is provide here yet (the syntax TV_GetParent(hitem, true) could be supported
  15479.         // if it's ever needed).
  15480.         aResultToken.value_int64 = SendMessage(control_hwnd, TVM_GETNEXTITEM, flag, (LPARAM)hitem);
  15481.         return;
  15482.     }
  15483.  
  15484.     // Since above didn't return, this TV_GetNext's 2-parameter mode, which has an expanded scope that includes
  15485.     // not just siblings, but also children and parents.  This allows a tree to be traversed from top to bottom
  15486.     // without the script having to do something fancy.
  15487.     char first_char_upper = toupper(*omit_leading_whitespace(ExprTokenToString(*aParam[1], buf))); // Resolve parameter #2.
  15488.     bool search_checkmark;
  15489.     if (first_char_upper == 'C')
  15490.         search_checkmark = true;
  15491.     else if (first_char_upper == 'F')
  15492.         search_checkmark = false;
  15493.     else // Reserve other option letters/words for future use by being somewhat strict.
  15494.         return; // Retain the default value of 0 set for aResultToken.value_int64 higher above.
  15495.  
  15496.     // When an actual item was specified, search begins at the item *after* it.  Otherwise (when NULL):
  15497.     // It's a special mode that always considers the root node first.  Otherwise, there would be no way
  15498.     // to start the search at the very first item in the tree to find out whether it's checked or not.
  15499.     hitem = GetNextTreeItem(control_hwnd, hitem); // Handles the comment above.
  15500.     if (!search_checkmark) // Simple tree traversal, so just return the next item (if any).
  15501.     {
  15502.         aResultToken.value_int64 = (__int64)hitem; // OK if NULL.
  15503.         return;
  15504.     }
  15505.  
  15506.     // Otherwise, search for the next item having a checkmark. For performance, it seems best to assume that
  15507.     // the control has the checkbox style (the script would realistically never call it otherwise, so the
  15508.     // control's style isn't checked.
  15509.     for (; hitem; hitem = GetNextTreeItem(control_hwnd, hitem))
  15510.         if (TreeView_GetCheckState(control_hwnd, hitem) == 1) // 0 means unchecked, -1 means "no checkbox image".
  15511.         {
  15512.             aResultToken.value_int64 = (__int64)hitem;
  15513.             return;
  15514.         }
  15515.     // Since above didn't return, the entire tree starting at the specified item has been searched,
  15516.     // with no match found.  Retain the default value of 0 set for aResultToken.value_int64 higher above.
  15517. }
  15518.  
  15519.  
  15520.  
  15521. void BIF_TV_Get(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  15522. // LV_Get()
  15523. // Returns: Varies depending on param #2.
  15524. // Parameters:
  15525. //    1: HTREEITEM.
  15526. //    2: Name of attribute to get.
  15527. // LV_GetText()
  15528. // Returns: 1 on success and 0 on failure.
  15529. // Parameters:
  15530. //    1: Output variable (doing it this way allows success/fail return value to more closely mirror the API and
  15531. //       simplifies the code since there is currently no easy means of passing back large strings to our caller).
  15532. //    2: HTREEITEM.
  15533. {
  15534.     bool get_text = (toupper(aResultToken.marker[6]) == 'T'); // Union's marker initially contains the function name. e.g. TV_Get[T]ext.
  15535.     char *buf = aResultToken.buf; // Must be saved early since below overwrites the union (better maintainability too).
  15536.     aResultToken.value_int64 = 0; // Set default return value. Must be done only after consulting marker above.
  15537.     // Above sets default result in case of early return.  For code reduction, a zero is returned for all
  15538.     // the following conditions:
  15539.     // Window doesn't exist.
  15540.     // Control doesn't exist (i.e. no TreeView in window).
  15541.     // Item not found in TreeView.
  15542.     // And others.
  15543.  
  15544.     if (!g_gui[g.GuiDefaultWindowIndex])
  15545.         return;
  15546.     GuiType &gui = *g_gui[g.GuiDefaultWindowIndex]; // Always operate on thread's default window to simplify the syntax.
  15547.     if (!gui.mCurrentTreeView)
  15548.         return;
  15549.     HWND control_hwnd = gui.mCurrentTreeView->hwnd;
  15550.  
  15551.     if (!get_text)
  15552.     {
  15553.         // Loadtime validation has ensured that param #1 and #2 are present for all these cases.
  15554.         HTREEITEM hitem = (HTREEITEM)ExprTokenToInt64(*aParam[0]);
  15555.         UINT state_mask;
  15556.         switch (toupper(*omit_leading_whitespace(ExprTokenToString(*aParam[1], buf))))
  15557.         {
  15558.         case 'E': state_mask = TVIS_EXPANDED; break; // Expanded
  15559.         case 'C': state_mask = TVIS_STATEIMAGEMASK; break; // Checked
  15560.         case 'B': state_mask = TVIS_BOLD; break; // Bold
  15561.         //case 'S' for "Selected" is not provided because TV_GetSelection() seems to cover that well enough.
  15562.         //case 'P' for "is item a parent?" is not provided because TV_GetChild() seems to cover that well enough.
  15563.         // (though it's possible that retrieving TVITEM's cChildren would perform a little better).
  15564.         }
  15565.         // Below seems to be need a bit-AND with state_mask to work properly, at least on XP SP2.  Otherwise,
  15566.         // extra bits are present such as 0x2002 for "expanded" when it's supposed to be either 0x00 or 0x20.
  15567.         UINT result = state_mask & (UINT)SendMessage(control_hwnd, TVM_GETITEMSTATE, (WPARAM)hitem, state_mask);
  15568.         if (state_mask == TVIS_STATEIMAGEMASK)
  15569.         {
  15570.             if (result == 0x2000) // It has a checkmark state image.
  15571.                 aResultToken.value_int64 = (size_t)hitem; // Override 0 set earlier. More useful than returning 1 since it allows function-call nesting.
  15572.         }
  15573.         else // For all others, anything non-zero means the flag is present.
  15574.             if (result)
  15575.                 aResultToken.value_int64 = (size_t)hitem; // Override 0 set earlier. More useful than returning 1 since it allows function-call nesting.
  15576.         return;
  15577.     }
  15578.  
  15579.     // Since above didn't return, this is LV_GetText().
  15580.     // Loadtime validation has ensured that param #1 and #2 are present.
  15581.     if (aParam[0]->symbol != SYM_VAR) // No output variable. Supporting a NULL for the purpose of checking for the existence of an item seems too rarely needed.
  15582.         return;
  15583.     Var &output_var = *aParam[0]->var;
  15584.  
  15585.     char text_buf[LV_TEXT_BUF_SIZE]; // i.e. uses same size as ListView.
  15586.     TVITEM tvi;
  15587.     tvi.hItem = (HTREEITEM)ExprTokenToInt64(*aParam[1]);
  15588.     tvi.mask = TVIF_TEXT;
  15589.     tvi.pszText = text_buf;
  15590.     tvi.cchTextMax = LV_TEXT_BUF_SIZE - 1; // -1 because of nagging doubt about size vs. length. Some MSDN examples subtract one), such as TabCtrl_GetItem()'s cchTextMax.
  15591.  
  15592.     if (SendMessage(control_hwnd, TVM_GETITEM, 0, (LPARAM)&tvi))
  15593.     {
  15594.         // Must use tvi.pszText vs. text_buf because MSDN says: "Applications should not assume that the text will
  15595.         // necessarily be placed in the specified buffer. The control may instead change the pszText member
  15596.         // of the structure to point to the new text rather than place it in the buffer."
  15597.         output_var.Assign(tvi.pszText);
  15598.         aResultToken.value_int64 = (size_t)tvi.hItem; // More useful than returning 1 since it allows function-call nesting.
  15599.     }
  15600.     else // On failure, it seems best to also clear the output var for better consistency and in case the script doesn't check the return value.
  15601.         output_var.Assign();
  15602.         // And leave aResultToken.value_int64 set to its default of 0.
  15603. }
  15604.  
  15605.  
  15606.  
  15607. void BIF_IL_Create(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  15608. // Returns: Handle to the new image list, or 0 on failure.
  15609. // Parameters:
  15610. // 1: Initial image count (ImageList_Create() ignores values <=0, so no need for error checking).
  15611. // 2: Grow count (testing shows it can grow multiple times, even when this is set <=0, so it's apparently only a performance aid)
  15612. // 3: Width of each image (overloaded to mean small icon size when omitted or false, large icon size otherwise).
  15613. // 4: Future: Height of each image [if this param is present and >0, it would mean param 3 is not being used in its TRUE/FALSE mode)
  15614. // 5: Future: Flags/Color depth
  15615. {
  15616.     // So that param3 can be reserved as a future "specified width" param, to go along with "specified height"
  15617.     // after it, only when the parameter is both present and numerically zero are large icons used.  Otherwise,
  15618.     // small icons are used.
  15619.     int param3 = aParamCount > 2 ? (int)ExprTokenToInt64(*aParam[2]) : 0;
  15620.     aResultToken.value_int64 = (__int64)ImageList_Create(GetSystemMetrics(param3 ? SM_CXICON : SM_CXSMICON)
  15621.         , GetSystemMetrics(param3 ? SM_CYICON : SM_CYSMICON)
  15622.         , ILC_MASK | ILC_COLOR32  // ILC_COLOR32 or at least something higher than ILC_COLOR is necessary to support true-color icons.
  15623.         , aParamCount > 0 ? (int)ExprTokenToInt64(*aParam[0]) : 2    // cInitial. 2 seems a better default than one, since it might be common to have only two icons in the list.
  15624.         , aParamCount > 1 ? (int)ExprTokenToInt64(*aParam[1]) : 5);  // cGrow.  Somewhat arbitrary default.
  15625. }
  15626.  
  15627.  
  15628.  
  15629. void BIF_IL_Destroy(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  15630. // Returns: 1 on success and 0 on failure.
  15631. // Parameters:
  15632. // 1: HIMAGELIST obtained from somewhere such as IL_Create().
  15633. {
  15634.     // Load-time validation has ensured there is at least one parameter.
  15635.     // Returns nonzero if successful, or zero otherwise, so force it to conform to TRUE/FALSE for
  15636.     // better consistency with other functions:
  15637.     aResultToken.value_int64 = ImageList_Destroy((HIMAGELIST)ExprTokenToInt64(*aParam[0])) ? 1 : 0;
  15638. }
  15639.  
  15640.  
  15641.  
  15642. void BIF_IL_Add(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
  15643. // Returns: the one-based index of the newly added icon, or zero on failure.
  15644. // Parameters:
  15645. // 1: HIMAGELIST: Handle of an existing ImageList.
  15646. // 2: Filename from which to load the icon or bitmap.
  15647. // 3: Icon number within the filename (or mask color for non-icon images).
  15648. // 4: The mere presence of this parameter indicates that param #3 is mask RGB-color vs. icon number.
  15649. //    This param's value should be "true" to resize the image to fit the image-list's size or false
  15650. //    to divide up the image into a series of separate images based on its width.
  15651. //    (this parameter could be overloaded to be the filename containing the mask image, or perhaps an HBITMAP
  15652. //    provided directly by the script)
  15653. // 5: Future: can be the scaling height to go along with an overload of #4 as the width.  However,
  15654. //    since all images in an image list are of the same size, the use of this would be limited to
  15655. //    only those times when the imagelist would be scaled prior to dividing it into separate images.
  15656. // The parameters above (at least #4) can be overloaded in the future calling ImageList_GetImageInfo() to determine
  15657. // whether the imagelist has a mask.
  15658. {
  15659.     char *buf = aResultToken.buf; // Must be saved early since below overwrites the union (better maintainability too).
  15660.     aResultToken.value_int64 = 0; // Set default in case of early return.
  15661.     HIMAGELIST himl = (HIMAGELIST)ExprTokenToInt64(*aParam[0]); // Load-time validation has ensured there is a first parameter.
  15662.     if (!himl)
  15663.         return;
  15664.  
  15665.     int param3 = (aParamCount > 2) ? (int)ExprTokenToInt64(*aParam[2]) : 0;
  15666.     int icon_number, width = 0, height = 0; // Zero width/height causes image to be loaded at its actual width/height.
  15667.     if (aParamCount > 3) // Presence of fourth parameter switches mode to be "load a non-icon image".
  15668.     {
  15669.         icon_number = 0; // Zero means "load icon or bitmap (doesn't matter)".
  15670.         if (ExprTokenToInt64(*aParam[3])) // A value of True indicates that the image should be scaled to fit the imagelist's image size.
  15671.             ImageList_GetIconSize(himl, &width, &height); // Determine the width/height to which it should be scaled.
  15672.         //else retain defaults of zero for width/height, which loads the image at actual size, which in turn
  15673.         // lets ImageList_AddMasked() divide it up into separate images based on its width.
  15674.     }
  15675.     else
  15676.         icon_number = param3; // LoadPicture() properly handles any wrong/negative value that might be here.
  15677.  
  15678.     int image_type;
  15679.     HBITMAP hbitmap = LoadPicture(ExprTokenToString(*aParam[1], buf) // Load-time validation has ensured there are at least two parameters.
  15680.         , width, height, image_type, icon_number, false); // Defaulting to "false" for "use GDIplus" provides more consistent appearance across multiple OSes.
  15681.     if (!hbitmap)
  15682.         return;
  15683.  
  15684.     if (image_type == IMAGE_BITMAP) // In this mode, param3 is always assumed to be an RGB color.
  15685.     {
  15686.         // Return the index of the new image or 0 on failure.
  15687.         aResultToken.value_int64 = ImageList_AddMasked(himl, hbitmap, rgb_to_bgr((int)param3)) + 1; // +1 to convert to one-based.
  15688.         DeleteObject(hbitmap);
  15689.     }
  15690.     else // ICON or CURSOR.
  15691.     {
  15692.         // Return the index of the new image or 0 on failure.
  15693.         aResultToken.value_int64 = ImageList_AddIcon(himl, (HICON)hbitmap) + 1; // +1 to convert to one-based.
  15694.         DestroyIcon((HICON)hbitmap); // Works on cursors too.  See notes in LoadPicture().
  15695.     }
  15696. }
  15697.  
  15698.  
  15699.  
  15700.  
  15701. ////////////////////////////////////////////////////////
  15702. // HELPER FUNCTIONS FOR TOKENS AND BUILT-IN FUNCTIONS //
  15703. ////////////////////////////////////////////////////////
  15704.  
  15705. __int64 ExprTokenToInt64(ExprTokenType &aToken)
  15706. // Converts the contents of aToken to a 64-bit int.
  15707. {
  15708.     // Some callers, such as those that cast our return value to UINT, rely on the use of 64-bit to preserve
  15709.     // unsigned values and also wrap any signed values into the unsigned domain.
  15710.     switch (aToken.symbol)
  15711.     {
  15712.         case SYM_INTEGER: return aToken.value_int64; // Fixed in v1.0.45 not to cast to int.
  15713.         case SYM_FLOAT: return (int)aToken.value_double;
  15714.         case SYM_VAR: return ATOI64(aToken.var->Contents()); // Fixed in v1.0.45 to use ATOI64 vs. ATOI().
  15715.         default: // SYM_STRING or SYM_OPERAND
  15716.             return ATOI64(aToken.marker); // Fixed in v1.0.45 to use ATOI64 vs. ATOI().
  15717.     }
  15718. }
  15719.  
  15720.  
  15721.  
  15722. double ExprTokenToDouble(ExprTokenType &aToken)
  15723. // Converts the contents of aToken to a double.
  15724. {
  15725.     switch (aToken.symbol)
  15726.     {
  15727.         case SYM_INTEGER: return (double)aToken.value_int64;
  15728.         case SYM_FLOAT: return aToken.value_double;
  15729.         case SYM_VAR: return ATOF(aToken.var->Contents());
  15730.         default: // SYM_STRING or SYM_OPERAND
  15731.             return ATOF(aToken.marker);
  15732.     }
  15733. }
  15734.  
  15735.  
  15736.  
  15737. char *ExprTokenToString(ExprTokenType &aToken, char *aBuf)
  15738. // Caller has ensured that any SYM_VAR's Type() is VAR_NORMAL.
  15739. // Returns "" on failure to simplify logic in callers.  Otherwise, it returns either aBuf (if aBuf was needed
  15740. // for the conversion) or the token's own string.  Caller has ensured that aBuf is at least
  15741. // MAX_FORMATTED_NUMBER_LENGTH+1 in size.
  15742. {
  15743.     switch (aToken.symbol)
  15744.     {
  15745.     case SYM_STRING:
  15746.     case SYM_OPERAND:
  15747.         return aToken.marker;
  15748.     case SYM_VAR: // Caller has ensured that any SYM_VAR's Type() is VAR_NORMAL.
  15749.         return aToken.var->Contents();
  15750.     case SYM_INTEGER:
  15751.         return ITOA64(aToken.value_int64, aBuf);
  15752.     case SYM_FLOAT:
  15753.         snprintf(aBuf, MAX_FORMATTED_NUMBER_LENGTH + 1, g.FormatFloat, aToken.value_double);
  15754.         return aBuf;
  15755.     default: // Not an operand.
  15756.         return "";
  15757.     }
  15758. }
  15759.  
  15760.  
  15761.  
  15762. ResultType ExprTokenToDoubleOrInt(ExprTokenType &aToken)
  15763. // Converts aToken's contents to a numeric value, either int or float (whichever is more appropriate).
  15764. // Returns FAIL when aToken isn't an operand or is but contains a string that isn't purely numeric.
  15765. {
  15766.     char *str;
  15767.     switch (aToken.symbol)
  15768.     {
  15769.         case SYM_INTEGER:
  15770.         case SYM_FLOAT:
  15771.             return OK;
  15772.         case SYM_VAR:
  15773.             str = aToken.var->Contents();
  15774.             break;
  15775.         case SYM_STRING:   // v1.0.40.06: Fixed to be listed explicitly so that "default" case can return failure.
  15776.         case SYM_OPERAND:
  15777.             str = aToken.marker;
  15778.             break;
  15779.         default:  // Not an operand. Haven't found a way to produce this situation yet, but safe to assume it's possible.
  15780.             return FAIL;
  15781.     }
  15782.     // Since above didn't return, interpret "str" as a number.
  15783.     switch (aToken.symbol = IsPureNumeric(str, true, false, true))
  15784.     {
  15785.     case PURE_INTEGER:
  15786.         aToken.value_int64 = ATOI64(str);
  15787.         break;
  15788.     case PURE_FLOAT:
  15789.         aToken.value_double = ATOF(str);
  15790.         break;
  15791.     default: // Not a pure number.
  15792.         aToken.marker = ""; // For completeness.  Some callers such as BIF_Abs() rely on this being done.
  15793.         return FAIL;
  15794.     }
  15795.     return OK; // Since above didn't return, indicate success.
  15796. }
  15797.  
  15798.  
  15799.  
  15800. int ConvertJoy(char *aBuf, int *aJoystickID, bool aAllowOnlyButtons)
  15801. // The caller TextToKey() currently relies on the fact that when aAllowOnlyButtons==true, a value
  15802. // that can fit in a sc_type (USHORT) is returned, which is true since the joystick buttons
  15803. // are very small numbers (JOYCTRL_1==12).
  15804. {
  15805.     if (aJoystickID)
  15806.         *aJoystickID = 0;  // Set default output value for the caller.
  15807.     if (!aBuf || !*aBuf) return JOYCTRL_INVALID;
  15808.     char *aBuf_orig = aBuf;
  15809.     for (; *aBuf >= '0' && *aBuf <= '9'; ++aBuf); // self-contained loop to find the first non-digit.
  15810.     if (aBuf > aBuf_orig) // The string starts with a number.
  15811.     {
  15812.         int joystick_id = ATOI(aBuf_orig) - 1;
  15813.         if (joystick_id < 0 || joystick_id >= MAX_JOYSTICKS)
  15814.             return JOYCTRL_INVALID;
  15815.         if (aJoystickID)
  15816.             *aJoystickID = joystick_id;  // Use ATOI vs. atoi even though hex isn't supported yet.
  15817.     }
  15818.  
  15819.     if (!strnicmp(aBuf, "Joy", 3))
  15820.     {
  15821.         if (IsPureNumeric(aBuf + 3, false, false))
  15822.         {
  15823.             int offset = ATOI(aBuf + 3);
  15824.             if (offset < 1 || offset > MAX_JOY_BUTTONS)
  15825.                 return JOYCTRL_INVALID;
  15826.             return JOYCTRL_1 + offset - 1;
  15827.         }
  15828.     }
  15829.     if (aAllowOnlyButtons)
  15830.         return JOYCTRL_INVALID;
  15831.  
  15832.     // Otherwise:
  15833.     if (!stricmp(aBuf, "JoyX")) return JOYCTRL_XPOS;
  15834.     if (!stricmp(aBuf, "JoyY")) return JOYCTRL_YPOS;
  15835.     if (!stricmp(aBuf, "JoyZ")) return JOYCTRL_ZPOS;
  15836.     if (!stricmp(aBuf, "JoyR")) return JOYCTRL_RPOS;
  15837.     if (!stricmp(aBuf, "JoyU")) return JOYCTRL_UPOS;
  15838.     if (!stricmp(aBuf, "JoyV")) return JOYCTRL_VPOS;
  15839.     if (!stricmp(aBuf, "JoyPOV")) return JOYCTRL_POV;
  15840.     if (!stricmp(aBuf, "JoyName")) return JOYCTRL_NAME;
  15841.     if (!stricmp(aBuf, "JoyButtons")) return JOYCTRL_BUTTONS;
  15842.     if (!stricmp(aBuf, "JoyAxes")) return JOYCTRL_AXES;
  15843.     if (!stricmp(aBuf, "JoyInfo")) return JOYCTRL_INFO;
  15844.     return JOYCTRL_INVALID;
  15845. }
  15846.  
  15847.  
  15848.  
  15849. bool ScriptGetKeyState(vk_type aVK, KeyStateTypes aKeyStateType)
  15850. // Returns true if "down", false if "up".
  15851. {
  15852.     if (!aVK) // Assume "up" if indeterminate.
  15853.         return false;
  15854.  
  15855.     switch (aKeyStateType)
  15856.     {
  15857.     case KEYSTATE_TOGGLE: // Whether a toggleable key such as CapsLock is currently turned on.
  15858.         // Under Win9x, at least certain versions and for certain hardware, this
  15859.         // doesn't seem to be always accurate, especially when the key has just
  15860.         // been toggled and the user hasn't pressed any other key since then.
  15861.         // I tried using GetKeyboardState() instead, but it produces the same
  15862.         // result.  Therefore, I've documented this as a limitation in the help file.
  15863.         // In addition, this was attempted but it didn't seem to help:
  15864.         //if (g_os.IsWin9x())
  15865.         //{
  15866.         //    DWORD fore_thread = GetWindowThreadProcessId(GetForegroundWindow(), NULL);
  15867.         //    bool is_attached_my_to_fore = false;
  15868.         //    if (fore_thread && fore_thread != g_MainThreadID)
  15869.         //        is_attached_my_to_fore = AttachThreadInput(g_MainThreadID, fore_thread, TRUE) != 0;
  15870.         //    output_var->Assign(IsKeyToggledOn(aVK) ? "D" : "U");
  15871.         //    if (is_attached_my_to_fore)
  15872.         //        AttachThreadInput(g_MainThreadID, fore_thread, FALSE);
  15873.         //    return OK;
  15874.         //}
  15875.         //else
  15876.         return IsKeyToggledOn(aVK); // This also works for the INSERT key, but only on XP (and possibly Win2k).
  15877.     case KEYSTATE_PHYSICAL: // Physical state of key.
  15878.         if (IsMouseVK(aVK)) // mouse button
  15879.         {
  15880.             if (g_MouseHook) // mouse hook is installed, so use it's tracking of physical state.
  15881.                 return g_PhysicalKeyState[aVK] & STATE_DOWN;
  15882.             else // Even for Win9x/NT, it seems slightly better to call this rather than IsKeyDown9xNT():
  15883.                 return IsKeyDownAsync(aVK);
  15884.         }
  15885.         else // keyboard
  15886.         {
  15887.             if (g_KeybdHook)
  15888.             {
  15889.                 // Since the hook is installed, use its value rather than that from
  15890.                 // GetAsyncKeyState(), which doesn't seem to return the physical state.
  15891.                 // But first, correct the hook modifier state if it needs it.  See comments
  15892.                 // in GetModifierLRState() for why this is needed:
  15893.                 if (KeyToModifiersLR(aVK))    // It's a modifier.
  15894.                     GetModifierLRState(true); // Correct hook's physical state if needed.
  15895.                 return g_PhysicalKeyState[aVK] & STATE_DOWN;
  15896.             }
  15897.             else // Even for Win9x/NT, it seems slightly better to call this rather than IsKeyDown9xNT():
  15898.                 return IsKeyDownAsync(aVK);
  15899.         }
  15900.     } // switch()
  15901.  
  15902.     // Otherwise, use the default state-type: KEYSTATE_LOGICAL
  15903.     if (g_os.IsWin9x() || g_os.IsWinNT4())
  15904.         return IsKeyDown9xNT(aVK); // See its comments for why it's more reliable on these OSes.
  15905.     else
  15906.         // On XP/2K at least, a key can be physically down even if it isn't logically down,
  15907.         // which is why the below specifically calls IsKeyDown2kXP() rather than some more
  15908.         // comprehensive method such as consulting the physical key state as tracked by the hook:
  15909.         // v1.0.42.01: For backward compatibility, the following hasn't been changed to IsKeyDownAsync().
  15910.         // For example, a script might rely on being able to detect whether Control was down at the
  15911.         // time the current Gui thread was launched rather than whether than whether it's down right now.
  15912.         // Another example is the journal playback hook: when a window owned by the script receives
  15913.         // such a keystroke, only GetKeyState() can detect the changed state of the key, not GetAsyncKeyState().
  15914.         // A new mode can be added to KeyWait & GetKeyState if Async is ever explicitly needed.
  15915.         return IsKeyDown2kXP(aVK);
  15916.         // Known limitation: For some reason, both the above and IsKeyDown9xNT() will indicate
  15917.         // that the CONTROL key is up whenever RButton is down, at least if the mouse hook is
  15918.         // installed without the keyboard hook.  No known explanation.
  15919. }
  15920.  
  15921.  
  15922.  
  15923. double ScriptGetJoyState(JoyControls aJoy, int aJoystickID, ExprTokenType &aToken, bool aUseBoolForUpDown)
  15924. // Caller must ensure that aToken.marker is a buffer large enough to handle the longest thing put into
  15925. // it here, which is currently jc.szPname (size=32). Caller has set aToken.symbol to be SYM_STRING.
  15926. // For buttons: Returns 0 if "up", non-zero if down.
  15927. // For axes and other controls: Returns a number indicating that controls position or status.
  15928. // If there was a problem determining the position/state, aToken is made blank and zero is returned.
  15929. // Also returns zero in cases where a non-numerical result is requested, such as the joystick name.
  15930. // In those cases, caller should use aToken.marker as the result.
  15931. {
  15932.     // Set default in case of early return.
  15933.     *aToken.marker = '\0'; // Blank vs. string "0" serves as an indication of failure.
  15934.  
  15935.     if (!aJoy) // Currently never called this way.
  15936.         return 0; // And leave aToken set to blank.
  15937.  
  15938.     bool aJoy_is_button = IS_JOYSTICK_BUTTON(aJoy);
  15939.  
  15940.     JOYCAPS jc;
  15941.     if (!aJoy_is_button && aJoy != JOYCTRL_POV)
  15942.     {
  15943.         // Get the joystick's range of motion so that we can report position as a percentage.
  15944.         if (joyGetDevCaps(aJoystickID, &jc, sizeof(JOYCAPS)) != JOYERR_NOERROR)
  15945.             ZeroMemory(&jc, sizeof(jc));  // Zero it on failure, for use of the zeroes later below.
  15946.     }
  15947.  
  15948.     // Fetch this struct's info only if needed:
  15949.     JOYINFOEX jie;
  15950.     if (aJoy != JOYCTRL_NAME && aJoy != JOYCTRL_BUTTONS && aJoy != JOYCTRL_AXES && aJoy != JOYCTRL_INFO)
  15951.     {
  15952.         jie.dwSize = sizeof(JOYINFOEX);
  15953.         jie.dwFlags = JOY_RETURNALL;
  15954.         if (joyGetPosEx(aJoystickID, &jie) != JOYERR_NOERROR)
  15955.             return 0; // And leave aToken set to blank.
  15956.         if (aJoy_is_button)
  15957.         {
  15958.             bool is_down = ((jie.dwButtons >> (aJoy - JOYCTRL_1)) & (DWORD)0x01);
  15959.             if (aUseBoolForUpDown) // i.e. Down==true and Up==false
  15960.             {
  15961.                 aToken.symbol = SYM_INTEGER; // Override default type.
  15962.                 aToken.value_int64 = is_down; // Forced to be 1 or 0 above, since it's "bool".
  15963.             }
  15964.             else
  15965.             {
  15966.                 aToken.marker[0] = is_down ? 'D' : 'U';
  15967.                 aToken.marker[1] = '\0';
  15968.             }
  15969.             return is_down;
  15970.         }
  15971.     }
  15972.  
  15973.     // Otherwise:
  15974.     UINT range;
  15975.     char *buf_ptr;
  15976.     double result_double;  // Not initialized to help catch bugs.
  15977.  
  15978.     switch(aJoy)
  15979.     {
  15980.     case JOYCTRL_XPOS:
  15981.         range = (jc.wXmax > jc.wXmin) ? jc.wXmax - jc.wXmin : 0;
  15982.         result_double = range ? 100 * (double)jie.dwXpos / range : jie.dwXpos;
  15983.         break;
  15984.     case JOYCTRL_YPOS:
  15985.         range = (jc.wYmax > jc.wYmin) ? jc.wYmax - jc.wYmin : 0;
  15986.         result_double = range ? 100 * (double)jie.dwYpos / range : jie.dwYpos;
  15987.         break;
  15988.     case JOYCTRL_ZPOS:
  15989.         range = (jc.wZmax > jc.wZmin) ? jc.wZmax - jc.wZmin : 0;
  15990.         result_double = range ? 100 * (double)jie.dwZpos / range : jie.dwZpos;
  15991.         break;
  15992.     case JOYCTRL_RPOS:  // Rudder or 4th axis.
  15993.         range = (jc.wRmax > jc.wRmin) ? jc.wRmax - jc.wRmin : 0;
  15994.         result_double = range ? 100 * (double)jie.dwRpos / range : jie.dwRpos;
  15995.         break;
  15996.     case JOYCTRL_UPOS:  // 5th axis.
  15997.         range = (jc.wUmax > jc.wUmin) ? jc.wUmax - jc.wUmin : 0;
  15998.         result_double = range ? 100 * (double)jie.dwUpos / range : jie.dwUpos;
  15999.         break;
  16000.     case JOYCTRL_VPOS:  // 6th axis.
  16001.         range = (jc.wVmax > jc.wVmin) ? jc.wVmax - jc.wVmin : 0;
  16002.         result_double = range ? 100 * (double)jie.dwVpos / range : jie.dwVpos;
  16003.         break;
  16004.  
  16005.     case JOYCTRL_POV:  // Need to explicitly compare against JOY_POVCENTERED because it's a WORD not a DWORD.
  16006.         if (jie.dwPOV == JOY_POVCENTERED)
  16007.         {
  16008.             // Retain default SYM_STRING type.
  16009.             strcpy(aToken.marker, "-1"); // Assign as string to ensure its written exactly as "-1". Documented behavior.
  16010.             return -1;
  16011.         }
  16012.         else
  16013.         {
  16014.             aToken.symbol = SYM_INTEGER; // Override default type.
  16015.             aToken.value_int64 = jie.dwPOV;
  16016.             return jie.dwPOV;
  16017.         }
  16018.         // No break since above always returns.
  16019.  
  16020.     case JOYCTRL_NAME:
  16021.         strcpy(aToken.marker, jc.szPname);
  16022.         return 0;  // Returns zero in cases where a non-numerical result is obtained.
  16023.  
  16024.     case JOYCTRL_BUTTONS:
  16025.         aToken.symbol = SYM_INTEGER; // Override default type.
  16026.         aToken.value_int64 = jc.wNumButtons;
  16027.         return jc.wNumButtons;  // wMaxButtons is the *driver's* max supported buttons.
  16028.  
  16029.     case JOYCTRL_AXES:
  16030.         aToken.symbol = SYM_INTEGER; // Override default type.
  16031.         aToken.value_int64 = jc.wNumAxes; // wMaxAxes is the *driver's* max supported axes.
  16032.         return jc.wNumAxes;
  16033.  
  16034.     case JOYCTRL_INFO:
  16035.         buf_ptr = aToken.marker;
  16036.         if (jc.wCaps & JOYCAPS_HASZ)
  16037.             *buf_ptr++ = 'Z';
  16038.         if (jc.wCaps & JOYCAPS_HASR)
  16039.             *buf_ptr++ = 'R';
  16040.         if (jc.wCaps & JOYCAPS_HASU)
  16041.             *buf_ptr++ = 'U';
  16042.         if (jc.wCaps & JOYCAPS_HASV)
  16043.             *buf_ptr++ = 'V';
  16044.         if (jc.wCaps & JOYCAPS_HASPOV)
  16045.         {
  16046.             *buf_ptr++ = 'P';
  16047.             if (jc.wCaps & JOYCAPS_POV4DIR)
  16048.                 *buf_ptr++ = 'D';
  16049.             if (jc.wCaps & JOYCAPS_POVCTS)
  16050.                 *buf_ptr++ = 'C';
  16051.         }
  16052.         *buf_ptr = '\0'; // Final termination.
  16053.         return 0;  // Returns zero in cases where a non-numerical result is obtained.
  16054.     } // switch()
  16055.  
  16056.     // If above didn't return, the result should now be in result_double.
  16057.     aToken.symbol = SYM_FLOAT; // Override default type.
  16058.     aToken.value_double = result_double;
  16059.     return result_double;
  16060. }
  16061.