<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<title type="html"><![CDATA[Серый форум &mdash; AutoHotkey]]></title>
	<link rel="self" href="http://forum.script-coding.com/extern.php?action=feed&amp;fid=2&amp;type=atom" />
	<updated>2026-07-04T23:47:07Z</updated>
	<generator>PunBB</generator>
	<id>http://forum.script-coding.com/index.php</id>
		<entry>
			<title type="html"><![CDATA[AHK: удобный скрипт для сохранения скриншотов]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?id=18604&amp;action=new" />
			<summary type="html"><![CDATA[<p>Скрипт является развитием <a href="https://forum.script-coding.com/viewtopic.php?pid=41921#p41921">этой</a> разработки.</p><p><strong>ScreenCatcher</strong> - скрипт для быстрых скриншотов на AutoHotkey v1</p><p><strong>ScreenCatcher</strong> делает снимки всего экрана, активного окна или выбранной области. Результат можно сохранить в файл, скопировать в буфер обмена или отправить на <a href="https://imgbb.com/">Imgbb</a>.</p><p>Скрипт пригодится, если нужно быстро вырезать фрагмент экрана, повторно снимать одну и ту же область, подогнать рамку выделения с точностью до пикселя, посмотреть снимок перед сохранением и при необходимости сразу получить ссылку на изображение.</p><p>Скрипт управляется через меню иконки в трее. Также свои контекстные меню есть у окна выделения и у окна предпросмотра скриншота.</p><p>Основные возможности</p><ul><li><p>снимок всего экрана</p></li><li><p>снимок активного окна</p></li><li><p>снимок области под окном выделения</p></li><li><p>быстрое выделение области мышью</p></li><li><p>сохранение снимка в файл</p></li><li><p>копирование снимка в буфер обмена</p></li><li><p>отправка снимка на Imgbb</p></li><li><p>предпросмотр снимков перед сохранением</p></li><li><p>перемещение и изменение размера окон предпросмотра</p></li><li><p>контекстное меню предпросмотра: сохранить, сохранить в текущем размере, удалить</p></li><li><p>точная подгонка границ окна выделения колесом мыши с модификаторами</p></li><li><p>настройка формата файла: png, jpg, bmp, tif, gif</p></li><li><p>настройка качества JPEG</p></li><li><p>выбор папки для сохранения файлов</p></li><li><p>захват курсора мыши на скриншоте</p></li><li><p>уведомления после сохранения</p></li><li><p>запуск вместе с Windows</p></li><li><p>настройка цвета и прозрачности окна выделения</p></li><li><p>два варианта подсказок при изменении размера окна выделения: измененные курсоры или цветные маркеры</p></li><li><p>настраиваемые горячие клавиши</p></li></ul><p>Два способа выбрать область для скриншота</p><p>В ScreenCatcher есть два разных сценария для снимка области.</p><p>1. Окно выделения</p><p>Это полупрозрачная окно, которое можно показать, скрыть, переместить, центрировать и точно изменить в размере, растягивая мышью. Скриншот делается с области, которая находится под этим окном. Скриншот можно делать по двойному клику по окну выделения, либо, если окно скрыто, по горячей клавише (по умолчанию F12)</p><p>Для точной подгонки окна можно использовать колесо мыши с модификаторами, когда окно выделения активно (кликните по нему, чтобы сделать активным):</p><ul><li><p>Ctrl + колесо - сдвиг левой границы</p></li><li><p>Shift + колесо - сдвиг верхней границы</p></li><li><p>Alt + колесо - сдвиг правой границы</p></li><li><p>Win + колесо - сдвиг нижней границы</p></li></ul><p>Каждый шаг колеса меняет выбранную границу на 1 пиксель, поэтому так удобно выставлять область скриншота точно по краям нужного элемента.</p><p>Такой способ удобен, когда нужно:</p><ul><li><p>несколько раз снимать одну и ту же область</p></li><li><p>заранее выставить точный размер кадра</p></li><li><p>аккуратно подогнать рамку по границам окна, изображения или элемента интерфейса</p></li><li><p>быстро повторять снимки без нового выделения мышью каждый раз</p></li></ul><p>2. Быстрое выделение мышью поверх экрана</p><p>Это режим одноразового выбора прямоугольника прямо поверх экрана. После вызова режима появляется крестик курсора; область выбирается протягиванием мыши. При выборе через F4 сначала включается режим ожидания, рядом с курсором показываются координаты. После нажатия левой кнопки начинается выделение. При быстром варианте через Ctrl+Alt+ЛКМ выделение начинается сразу, без предварительной плашки координат.</p><p>Такой способ удобен, когда нужно:</p><ul><li><p>быстро снять произвольный фрагмент экрана</p></li><li><p>не двигать основное окно выделения</p></li><li><p>один раз выбрать область и сразу отправить ее в общий механизм сохранения, буфера обмена, предпросмотра или Imgbb</p></li></ul><p>Отменить выбор можно клавишей Esc или правой кнопкой мыши.</p><p>Предпросмотр</p><p>Если включена опция предпросмотра, после захвата открывается окно со снимком. Его можно перемещать и менять в размере колесом мыши. Через контекстное меню можно сохранить изображение, сохранить его в текущем размере после масштабирования или удалить окно предпросмотра.</p><p>Это удобно, если перед сохранением нужно проверить кадр, подогнать размер или оставить на экране несколько сделанных снимков.</p><p>Вывод снимков</p><p>В настройках можно выбрать один или несколько способов вывода:</p><ul><li><p>в файл</p></li><li><p>в буфер обмена</p></li><li><p>на Imgbb.com</p></li></ul><p>Способы можно комбинировать. Например, снимок может одновременно сохраняться в файл, копироваться в буфер обмена и отправляться на Imgbb.</p><p>Настройка отправки на Imgbb</p><p>Чтобы отправка на Imgbb работала, нужно получить API key.</p><p>Для этого:</p><p>1. Зарегистрироваться на Imgbb:<br />&nbsp; &nbsp;<a href="https://imgbb.com/">https://imgbb.com/</a></p><p>2. Открыть страницу API:<br />&nbsp; &nbsp;<a href="https://api.imgbb.com/">https://api.imgbb.com/</a></p><p>3. Получить ключ API.</p><p>4. Открыть файл ScreenCatcher.ini рядом со скриптом.</p><p>5. Вписать ключ в секцию [Imgbb]:</p><div class="codebox"><pre><code>[Imgbb]
ClientID=YOUR_IMGBB_API_KEY</code></pre></div><p>6. В настройках ScreenCatcher на вкладке &quot;Вывод&quot; включить Imgbb.com.</p><p>Горячие клавиши по умолчанию</p><ul><li><p>F12 - скриншот области под окном выделения</p></li><li><p>Right Shift + PrintScreen - скриншот активного окна</p></li><li><p>PrintScreen - скриншот всего экрана</p></li><li><p>Pause - показать или скрыть окно выделения</p></li><li><p>ScrollLock - переместить окно выделения в центр экрана</p></li><li><p>Left Ctrl + Space - выделить окно предпросмотра</p></li><li><p>F4 - выбрать область для скриншота мышью поверх экрана</p></li><li><p>Ctrl + Alt + ЛКМ - быстро выбрать область для скриншота мышью</p></li></ul><p>Все горячие клавиши можно изменить в настройках. Если горячая клавиша не нужна, ее можно очистить.</p><p>Настройки</p><p>В окне настроек доступны вкладки:</p><ul><li><p>&quot;Цвет&quot; - цвет и прозрачность окна выделения</p></li><li><p>&quot;Вывод&quot; - куда отправлять скриншоты, формат файла, качество JPEG и папка сохранения</p></li><li><p>&quot;Настройки&quot; - захват курсора, предпросмотр, уведомления, запуск вместе с Windows и режим подсказок при изменении размера рамки</p></li><li><p>&quot;Горячие клавиши&quot; - назначение сочетаний клавиш и мыши</p></li></ul><p>Во время открытия окна настроек рабочие горячие клавиши ScreenCatcher временно отключаются, чтобы они не мешали вводу новых сочетаний в поля настройки.</p><p>Трей-меню и меню окна выделения</p><p>Основные действия доступны из меню в трее и из контекстного меню окна выделения:</p><ul><li><p>открыть настройки</p></li><li><p>сделать скриншот</p></li><li><p>показать или скрыть окно выделения</p></li><li><p>переместить окно выделения в центр экрана</p></li><li><p>открыть папку вывода</p></li><li><p>открыть справку</p></li><li><p>выйти из скрипта</p></li></ul><div class="codebox"><pre><code>#NoEnv
#SingleInstance, force
#MaxHotkeysPerInterval 200
SetWinDelay, 0
SetBatchLines, -1
SetTitleMatchMode, 3
CoordMode, Mouse
OnExit(&quot;ShutdownApplication&quot;)

global App := {Config: {}, Settings: {}, Menu: {}, Selection: {}, Windows: {}}

hIcon16 := CreateIconFromBase64(GetBase64String(16), 16)
Menu, Tray, Icon, HICON:*%hIcon16%

hIcon32 := CreateIconFromBase64(GetBase64String(32), 32)
DllCall(&quot;SendMessage&quot;, &quot;Ptr&quot;, A_ScriptHwnd, &quot;UInt&quot;, WM_SETICON := 0x80, &quot;Ptr&quot;, ICON_BIG := 1, &quot;Ptr&quot;, hIcon32)
App.GdipToken := GdipStartup()

				    ;  секция          ключ               имя переменной        умолчание
IniReadConfigSect(&quot;Color-Transparent&quot;, &quot;ARGB&quot;             , &quot;ARGB&quot;             , 0x82FFC700)

IniReadConfigSect(&quot;Output_Settings&quot;  , &quot;Output&quot;           , &quot;Output&quot;           , &quot;File&quot;
									 , &quot;Extension&quot;        , &quot;Ext&quot;              , &quot;png&quot;
									 , &quot;JPEG_Quality&quot;     , &quot;JPEGQuality&quot;      , 100
									 , &quot;Out_Directory&quot;    , &quot;OutDirectory&quot;     , A_ScriptDir)

IniReadConfigSect(&quot;Program_Settings&quot; , &quot;Capture_Cursor&quot;   , &quot;CaptureCursor&quot;    , 0
									 , &quot;Show_Preview&quot;     , &quot;ShowPreview&quot;      , 1
									 , &quot;Notify&quot;           , &quot;Notify&quot;           , 1
									 , &quot;Resize_Pointers&quot;  , &quot;ResizePointers&quot;   , &quot;Cursors&quot;
									 , &quot;InterpolationMode&quot;, &quot;InterpolationMode&quot;, 3)
										 
IniReadHotkeysSect(&quot;Hotkeys&quot;         , &quot;GetArea&quot;          , &quot;GetArea&quot;          , &quot;$F12&quot;
								     , &quot;GetActiveWindow&quot;  , &quot;GetActiveWindow&quot;  , &quot;&gt;+PrintScreen&quot;
								     , &quot;GetScreen&quot;        , &quot;GetScreen&quot;        , &quot;PrintScreen&quot;
								     , &quot;ShowHideSelWindow&quot;, &quot;ShowHideSelWindow&quot;, &quot;Pause&quot;
								     , &quot;SelWindowToCenter&quot;, &quot;SelWindowToCenter&quot;, &quot;ScrollLock&quot;
								     , &quot;SelectPreview&quot;    , &quot;SelectPreview&quot;    , &quot;&lt;^Space&quot;
								     , &quot;OpenOutputFolder&quot; , &quot;OpenOutputFolder&quot; , &quot; &quot;
								     , &quot;SelectAreaOverlay&quot;, &quot;SelectAreaOverlay&quot;, &quot;F4&quot;
								     , &quot;QuickSelectAreaOverlay&quot;, &quot;QuickSelectAreaOverlay&quot;, &quot;^LButton&quot;)
NormalizeLoadedSettings()

Menu, Tray, NoStandard
Menu, Tray, Add, Справка, ShowHelp
Menu, Tray, Add
Menu, Tray, Add, Вывод, ShowSettingsFromMenu
Menu, Tray, Add, Настройки, ShowSettingsFromMenu
Menu, Tray, Add
App.Menu.TrayHide := MenuHotkeyCaption(&quot;Скрыть окно выделения&quot;, App.Hotkeys.ShowHideSelWindow)
App.Menu.TrayShow := MenuHotkeyCaption(&quot;Показать окно выделения&quot;, App.Hotkeys.ShowHideSelWindow)
App.Menu.TrayCenter := MenuHotkeyCaption(&quot;Окно выделения в центр экрана&quot;, App.Hotkeys.SelWindowToCenter)
App.Menu.TrayOpenOutDir := MenuHotkeyCaption(&quot;Открыть папку вывода&quot;, App.Hotkeys.OpenOutputFolder)
Menu, Tray, Add, % App.Menu.TrayShow, ToggleSelectionWindow
Menu, Tray, Add, % App.Menu.TrayCenter, CenterSelectionWindow
Menu, Tray, Add, % App.Menu.TrayOpenOutDir, OpenOutputDirectory
if !A_IsCompiled {
	Menu, Tray, Add
	Menu, Tray, Add, Редактировать скрипт, EditScript
	Menu, Tray, Add, Перезагрузка, ReloadScript
}
Menu, Tray, Add
Menu, Tray, Add, Выход, ShutdownApplication

Menu, MainMenu, Add, Вывод, ShowSettingsFromMenu
Menu, MainMenu, Add, Настройки, ShowSettingsFromMenu
Menu, MainMenu, Add
App.Menu.MainArea := MenuHotkeyCaption(&quot;Сделать скриншот&quot;, App.Hotkeys.GetArea)
App.Menu.MainHide := MenuHotkeyCaption(&quot;Скрыть окно выделения&quot;, App.Hotkeys.ShowHideSelWindow)
App.Menu.MainOpenOutDir := MenuHotkeyCaption(&quot;Открыть папку вывода&quot;, App.Hotkeys.OpenOutputFolder)
Menu, MainMenu, Add, % App.Menu.MainArea, CaptureAreaScreenshot
Menu, MainMenu, Add, % App.Menu.MainHide, ToggleSelectionWindow
Menu, MainMenu, Add, % App.Menu.MainOpenOutDir, OpenOutputDirectory
Menu, MainMenu, Add
Menu, MainMenu, Add, Справка, ShowHelp

Menu, Interpolation, Add, Default, SetInterpolationMode
Menu, Interpolation, Add, LowQuality, SetInterpolationMode
Menu, Interpolation, Add, HighQuality, SetInterpolationMode
Menu, Interpolation, Add, Bilinear, SetInterpolationMode
Menu, Interpolation, Add, Bicubic, SetInterpolationMode
Menu, Interpolation, Add, NearestNeighbor, SetInterpolationMode
Menu, Interpolation, Add, HighQualityBilinear, SetInterpolationMode
Menu, Interpolation, Add, HighQualityBicubic, SetInterpolationMode
InterpolationModeString := &quot;Default|LowQuality|HighQuality|Bilinear|&quot;
	. &quot;Bicubic|NearestNeighbor|HighQualityBilinear|HighQualityBicubic&quot;
App.Menu.InterpolationItems := StrSplit(InterpolationModeString, &quot;|&quot;)

Menu, Interpolation, Check, % App.Menu.InterpolationItems[App.Config.InterpolationMode]

Menu, PreviewSaveToMenu, Add, файл, PreviewSaveToMenuHandling
Menu, PreviewSaveToMenu, Add, буфер обмена, PreviewSaveToMenuHandling
Menu, PreviewSaveToMenu, Add, imgbb.com, PreviewSaveToMenuHandling

Menu, PreviewSaveOriginalToMenu, Add, файл, PreviewSaveToMenuHandling
Menu, PreviewSaveOriginalToMenu, Add, буфер обмена, PreviewSaveToMenuHandling
Menu, PreviewSaveOriginalToMenu, Add, imgbb.com, PreviewSaveToMenuHandling

Menu, PreviewSaveCurrentToMenu, Add, файл, PreviewSaveCurrentToMenuHandling
Menu, PreviewSaveCurrentToMenu, Add, буфер обмена, PreviewSaveCurrentToMenuHandling
Menu, PreviewSaveCurrentToMenu, Add, imgbb.com, PreviewSaveCurrentToMenuHandling

Menu, ScreenShotMenu, Add, Сохранить в первоначальном размере, ScreenShotMenuHandling
Menu, ScreenShotMenu, Add, Сохранить в первоначальном размере в, :PreviewSaveOriginalToMenu
Menu, ScreenShotMenu, Add, Сохранить в текущем размере, ScreenShotMenuHandling
Menu, ScreenShotMenu, Add, Сохранить в текущем размере в, :PreviewSaveCurrentToMenu
Menu, ScreenShotMenu, Add, Удалить, ScreenShotMenuHandling

Menu, ScreenShotMenu2, Add, Интерполяция при изменении размеров, :Interpolation
Menu, ScreenShotMenu2, Add, Сохранить в, :PreviewSaveToMenu
Menu, ScreenShotMenu2, Add, Сохранить, ScreenShotMenuHandling
Menu, ScreenShotMenu2, Add, Удалить, ScreenShotMenuHandling

Gui, +LastFound -Caption +AlwaysOnTop +Owner
StoreSelectionWindowHwnd()
WinSet, Transparent, 0, % &quot;ahk_id &quot; App.Selection.Hwnd
Gui, Color, % Format(&quot;{:X}&quot;, App.Config.ARGB &amp; 0xFFFFFF)

Locations = x190 y190|x0 y190|x0 y0|x190 y0|x190 y95|x95 y190|x0 y95|x95 y0
App.Selection.MarkerHwnds := []
Loop, parse, Locations, |			; SS_BITMAP = 0xE
{
	Gui, Add, Text, % &quot;w10 h10 +0xE hidden hwndMarkerHwnd &quot; A_LoopField
	App.Selection.MarkerHwnds.Push(MarkerHwnd)
}
UpdateMarkerColor()

SetResizeHotkeyState(&quot;Off&quot;)

if !A_Args.Length() {
	CenterSelectionWindow()
} else {
	opts := &quot;x&quot; (A_ScreenWidth - 200)//2 &quot; y&quot; (A_ScreenHeight - 200)//2 &quot; h200 w200 Hide&quot;
	Gui, Show, % opts, ScreenСatcher
}
SetScriptHotkeys(1)

Esc := DllCall(&quot;RegisterWindowMessage&quot;, &quot;Str&quot;, &quot;Esc&quot;)
Wheel := DllCall(&quot;RegisterWindowMessage&quot;, &quot;Str&quot;, &quot;Wheel&quot;)

OnMessage( Esc,  &quot;WM_ESC&quot;)
OnMessage(Wheel, &quot;WM_WHEEL&quot;)
OnMessage(0x018, &quot;WM_SHOWWINDOW&quot;)
OnMessage(0x020, &quot;WM_OVERLAY_SETCURSOR&quot;)
OnMessage(0x201, &quot;WM_LBUTTONDOWN&quot;)
OnMessage(0x203, &quot;WM_LBUTTONDBLCLK&quot;)
OnMessage(0x204, &quot;WM_RBUTTONDOWN&quot;)
OnMessage(0x404, &quot;AHK_NOTIFYICON&quot;)
return

;###########################################################################################################
;*************************************** Окно Настроек *****************************************************
;###########################################################################################################

OpenSettingsWindow(ThisMenuItemPos) {
	HideHelpWindow()
	App.Config.RunOnWinStart := IsRunOnWindowsStartEnabled()
	SelectionHwnd := App.Selection.Hwnd
	Gui, 2:Default
	if WindowExistsByHwnd(App.Settings.WindowHwnd) {
		if WindowExistsByHwnd(App.Settings.HotkeysHwnd)
			Gui, 4:Hide
		GuiControl, Choose, % App.Settings.TabHwnd, % ThisMenuItemPos
		SuspendScriptHotkeysForSettings()
		Gui, 2:Show
		UpdateHotkeysTabVisibility()
		return
	}
	SavePreviousSettingsState()
	SuspendScriptHotkeysForSettings()
	Width := 310, Height := 230
	Gui, 2:+LastFound +hwndSettingsHwnd
	App.Settings.WindowHwnd := SettingsHwnd
	App.Windows[2] := Object(&quot;Hwnd&quot;, SettingsHwnd)
	Gui, Font, s10 q5, Calibri
	Gui, Add, Tab3, gUpdateHotkeysTabVisibility x0 y0 w%Width% h%Height% hwndSettingsTabHwnd AltSubmit -Wrap
		, % &quot;   Цвет| Вывод|Настройки|Горячие клавиши&quot;
	App.Settings.TabHwnd := SettingsTabHwnd
	GuiControl, Choose, % App.Settings.TabHwnd, % ThisMenuItemPos

;********************************** Первая вкладка &quot;Цвет&quot; ***********************************

	Gui, Add, Pic, % &quot;x10 y+10 h184 +0x8 w&quot; Width - 21		; SS_GRAYFRAME = 0x8

	Gui, Font, s10, Segoe UI
	Gui, Add, Text, % &quot;Center c5021AC x15 yp+10 w&quot; Width-30, Настройки цвета и прозрачности
	Gui, Font, s8 q5, Verdana
	Gui, Add, Text, x25 yp+37 w30 cRed Center, Red
	Gui, Add, Text, xp-2 yp+30 wp cGreen Center, Green
	Gui, Add, Text, xp yp+30 wp cBlue Center, Blue
	Gui, Add, Text, xp yp+30 wp cEECC00 Center, Trans

	ColorState := App.Settings.Color := Object()
	ColorState.Red := (App.Config.ARGB &amp; 0xFF0000)&gt;&gt;16
	ColorState.Green := (App.Config.ARGB &amp; 0xFF00)&gt;&gt;8
	ColorState.Blue := App.Config.ARGB &amp; 0xFF
	ColorState.Trans := App.Config.ARGB&gt;&gt;24

	Options := &quot;x65 w&quot; Width - 115 &quot; h20 Range0-255 ToolTip AltSubmit&quot;
	Gui, Add, Slider, gUpdateSelectionColorFromGui %Options% yp-91 hwndRedSliderHwnd, % ColorState.Red
	Gui, Add, Slider, gUpdateSelectionColorFromGui %Options% yp+30 hwndGreenSliderHwnd, % ColorState.Green
	Gui, Add, Slider, gUpdateSelectionColorFromGui %Options% yp+30 hwndBlueSliderHwnd, % ColorState.Blue
	Gui, Add, Slider, gUpdateSelectionTransparencyFromGui %Options% yp+30 hwndTransSliderHwnd, % ColorState.Trans

	Gui, Add, Text, xp+195 yp-89 w30 cRed Center hwndRedValueHwnd, % ColorState.Red
	Gui, Add, Text, xp yp+30 wp cGreen Center hwndGreenValueHwnd, % ColorState.Green
	Gui, Add, Text, xp yp+30 wp cBlue Center hwndBlueValueHwnd, % ColorState.Blue
	Gui, Add, Text, xp yp+30 wp cEECC00 Center hwndTransValueHwnd, % ColorState.Trans
	App.Settings.RedSliderHwnd := RedSliderHwnd
	App.Settings.GreenSliderHwnd := GreenSliderHwnd
	App.Settings.BlueSliderHwnd := BlueSliderHwnd
	App.Settings.TransSliderHwnd := TransSliderHwnd
	App.Settings.RedValueHwnd := RedValueHwnd
	App.Settings.GreenValueHwnd := GreenValueHwnd
	App.Settings.BlueValueHwnd := BlueValueHwnd
	App.Settings.TransValueHwnd := TransValueHwnd

;********************************* Вторая вкладка &quot;Вывод&quot; ************************************

	Gui, Tab, 2
	Gui, Font, s10, Segoe UI
	Gui, Add, Text, % &quot;Center c5021AC x5 y+6 w&quot; Width-10, Как сохранять скриншоты:
	Gui, Font, s8 q5, Verdana
	Gui, Add, Checkbox
		, % &quot;gUpdateOutputTargetFromGui x15 yp+25 hwndOutputFileHwnd&quot; (InStr(App.Config.Output, &quot;File&quot;) ? &quot; Checked&quot; : &quot;&quot;), В файл
	Gui, Add, Checkbox
		, % &quot;gUpdateOutputTargetFromGui xp+75 yp hwndOutputClipHwnd&quot; (InStr(App.Config.Output, &quot;Clip&quot;) ? &quot; Checked&quot; : &quot;&quot;), В буфер обмена
	Gui, Add, Checkbox
		, % &quot;gUpdateOutputTargetFromGui xp+125 yp hwndOutputImgbbHwnd&quot; (InStr(App.Config.Output, &quot;Imgbb&quot;) ? &quot; Checked&quot; : &quot;&quot;), Imgbb.com
	App.Settings.OutputFileHwnd := OutputFileHwnd
	App.Settings.OutputClipHwnd := OutputClipHwnd
	App.Settings.OutputImgbbHwnd := OutputImgbbHwnd

	Gui, Add, Pic, % &quot;x2 yp+26 h2 +0x8 w&quot; Width - 4		; SS_GRAYFRAME = 0x8

	Gui, Font, s10, Segoe UI
	Gui, Add, Text, % &quot;Center c5021AC x5 yp+9 w&quot; Width-10, Тип файлов для вывода
	Gui, Font, s8 q5, Verdana
	Gui, Add, Radio, % &quot;gSetOutputExtensionFromGui x25 yp+23 hwndExtensionPngHwnd&quot; (App.Config.Ext = &quot;png&quot; ? &quot; Checked&quot; : &quot;&quot;), png
	Gui, Add, Radio, % &quot;gSetOutputExtensionFromGui xp+55 yp hwndExtensionJpgHwnd&quot; (App.Config.Ext = &quot;jpg&quot; ? &quot; Checked&quot; : &quot;&quot;), jpg
	Gui, Add, Radio, % &quot;gSetOutputExtensionFromGui xp+55 yp hwndExtensionBmpHwnd&quot; (App.Config.Ext = &quot;bmp&quot; ? &quot; Checked&quot; : &quot;&quot;), bmp
	Gui, Add, Radio, % &quot;gSetOutputExtensionFromGui xp+55 yp hwndExtensionTifHwnd&quot; (App.Config.Ext = &quot;tif&quot; ? &quot; Checked&quot; : &quot;&quot;), tif
	Gui, Add, Radio, % &quot;gSetOutputExtensionFromGui xp+55 yp hwndExtensionGifHwnd&quot; (App.Config.Ext = &quot;gif&quot; ? &quot; Checked&quot; : &quot;&quot;), gif
	App.Settings.ExtensionPngHwnd := ExtensionPngHwnd
	App.Settings.ExtensionJpgHwnd := ExtensionJpgHwnd
	App.Settings.ExtensionBmpHwnd := ExtensionBmpHwnd
	App.Settings.ExtensionTifHwnd := ExtensionTifHwnd
	App.Settings.ExtensionGifHwnd := ExtensionGifHwnd

	Gui, Font, s10, Segoe UI
	Gui, Add, Text, Left c5021AC x20 yp+23, Качество jpg:
	Gui, Add, Slider
		, gSetJpegQuality xp+107 yp w128 h20 Range50-100 ToolTip AltSubmit hwndJpegQualitySliderHwnd, % App.Config.JPEGQuality
	Gui, Font, s8 q5, Verdana
	Gui, Add, Text, Center c5021AC xp+132 yp+2 w30 hwndJpegQualityPercentHwnd, % App.Config.JPEGQuality &quot;`%&quot;
	App.Settings.JpegQualitySliderHwnd := JpegQualitySliderHwnd
	App.Settings.JpegQualityPercentHwnd := JpegQualityPercentHwnd

	Gui, Add, Pic, % &quot;x2 yp+26 h2 +0x8 w&quot; Width - 4		; SS_GRAYFRAME = 0x8

	Gui, Font, s10, Segoe UI
	Gui, Add, Text, % &quot;Center c5021AC x5 yp+9 w&quot; Width-10, Папка для сохранения файлов
	Gui, Font, s8 q5, Verdana
	Gui, Add, Edit, % &quot;hwndOutDirectoryEditHwnd x10 yp+24 h21 w&quot; Width-50, % App.Config.OutDirectory
	App.Settings.OutDirectoryEditHwnd := OutDirectoryEditHwnd
	Gui, Font, s10
	Gui, Add, Button, % &quot;gSelectOutputDirectory x&quot; Width-35 &quot; yp hp w23&quot;, ...

;************************** Третья вкладка &quot;Настройки программы&quot; ****************************

	Gui, Tab, 3
	Gui, Font, s8 q5, Verdana
	Gui, Add, Checkbox
		, % &quot;x20 y+15 hwndCaptureCursorHwnd&quot; (App.Config.CaptureCursor ? &quot; Checked&quot; : &quot;&quot;)
		, Включать курсор в скриншот
	Gui, Add, Checkbox, % &quot;xp yp+25 hwndShowPreviewHwnd&quot; (App.Config.ShowPreview ? &quot; Checked&quot; : &quot;&quot;)
		, Показывать скриншот перед сохранением
	Gui, Add, Checkbox, % &quot;xp yp+25 hwndNotifyHwnd&quot; (App.Config.Notify ? &quot; Checked&quot; : &quot;&quot;)
		, Уведомлять о сохранении скриншота

	Gui, Add, Checkbox
		, % &quot;xp yp+25 hwndRunOnWinStartHwnd&quot; (App.Config.RunOnWinStart ? &quot; Checked&quot; : &quot;&quot;)
		, Запускать программу при загрузке системы
	App.Settings.CaptureCursorHwnd := CaptureCursorHwnd
	App.Settings.ShowPreviewHwnd := ShowPreviewHwnd
	App.Settings.NotifyHwnd := NotifyHwnd
	App.Settings.RunOnWinStartHwnd := RunOnWinStartHwnd
	Gui, Add, Checkbox, xp yp+25, Проверять обновления при запуске
	; Gui, Add, Pic, % &quot;x2 yp+30 h2 +0x8 w&quot; Width - 4		; SS_GRAYFRAME = 0x8

	Gui, Font, s10, Segoe UI
	Gui, Add, Text, % &quot;Center c5021AC x5 yp+25 w&quot; Width-10
		, Индикаторы при изменении размеров`nокна выделения
	Gui, Font, s8 q5, Verdana
	Gui, Add, Radio, xp+14 yp+40 hwndResizeCursorsHwnd, Изменённые курсоры
	Gui, Add, Radio, xp+153 yp hwndResizeMarkersHwnd, Цветные маркеры
	GuiControl,, % (App.Config.ResizePointers = &quot;Cursors&quot; ? ResizeCursorsHwnd : ResizeMarkersHwnd), 1
	App.Settings.ResizeCursorsHwnd := ResizeCursorsHwnd
	App.Settings.ResizeMarkersHwnd := ResizeMarkersHwnd
	
	Gui, Tab
	
;*************************** Четвёртая вкладка &quot;Горячие клавиши&quot; *****************************

	HotkeysTabPageHwnd := DllCall(&quot;GetWindow&quot;, &quot;Ptr&quot;, App.Settings.TabHwnd, &quot;UInt&quot;, GW_HWNDNEXT := 2)
	WinGetPos,,, W, H, ahk_id %HotkeysTabPageHwnd%
	
	Gui, 4: Default
	Gui, +Parent%HotkeysTabPageHwnd% -Caption +ToolWindow +hwndHotkeysHwnd
	App.Settings.HotkeysHwnd := HotkeysHwnd
	Gui, Add, Tab3, % &quot;gFocusSettingsSafeButton x&quot; 5 &quot; y&quot; 5 &quot; w&quot; W - 8 &quot; h&quot; H - 9 &quot; hwndHotkeysSubTabHwnd&quot;, Скриншоты|Окна скрипта|Служебные
	App.Settings.HotkeysSubTabHwnd := HotkeysSubTabHwnd
	Handles := []
	HotkeyControls := []
	HotkeyNames := ScreenCatcherHotkeyNames()
	for k, v in [ &quot;Скриншот под окном выделения&quot;
				, &quot;Скриншот активного окна&quot;
				, &quot;Скриншот всего экрана&quot;
				, &quot;Скрыть/показать окно выделения&quot;
				, &quot;Окно выделения в центр экрана&quot;
				, &quot;Выделить превью скриншота&quot;
				, &quot;Открыть папку вывода&quot;
				, &quot;Выбрать область для скриншота&quot;
				, &quot;Быстро выделить область для скриншота&quot; ]
	{
		if k = 4
			Gui, Tab, 2
		if k = 7
			Gui, Tab, 3
		Gui, Font, s10, Segoe UI
		Gui, Add, Text, % &quot;x0 y+8 w&quot; . (Width - 6) . &quot; Center c5021AC&quot;, %v%:
		Gui, Font, s11, calibri
		Gui, Add, Edit, % &quot;Center ReadOnly x15 y+5 h23 w&quot; . (Width-36) . &quot; hwndhHotkey&quot; . k
		SetStaticColor(hHotkey%k%, 0xFFFFFF)
		Handles.Push(hHotkey%k%)
		Options := &quot;KDMLR&quot;
		HotkeyControls.Push([HotkeyNames[k], hHotkey%k%, Options])
	}
	Hotkey_Register(HotkeyControls*)
	SetHotkeyControlsFromVars()
	Gui, Show, Hide x0 y0 w%W% h%H%
	App.Settings.MessageHandlers := {}
	App.Settings.MessageHandlers.WM_COMMAND := Func(&quot;WM_COMMAND&quot;).Bind(Handles)
	App.Settings.MessageHandlers.WM_SETCURSOR := Func(&quot;WM_SETCURSOR&quot;).Bind(Handles)
	OnMessage( 0x111, App.Settings.MessageHandlers.WM_COMMAND )
	OnMessage( 0x20, App.Settings.MessageHandlers.WM_SETCURSOR )
	
;***********************************************************************************************

	Gui, 2:Default
	
	Gui, Font, s8 q5, Verdana
	Gui, Add, Button, % &quot;gConfirmSettingsFromGui x&quot; Width - 286 &quot; y&quot; Height + 14 &quot; w85 h23&quot;, OK
	Gui, Add, Button, gApplySettingsFromGui xp+90 yp wp hp, Применить
	Gui, Add, Button, gCancelSettingsFromGui xp+90 yp wp hp, Отмена
	
	if WinExist(&quot;ahk_id &quot; SelectionHwnd) {
		WinGetPos, X1, Y1, W1, H1, ahk_id %SelectionHwnd%
		Gui, Show, % &quot;w&quot; Width-1 &quot; h&quot; Height+51 &quot; Hide&quot;, Настройки
		DetectHiddenWindows, On
		WinGetPos,,, W2, H2, % &quot;ahk_id &quot; App.Settings.WindowHwnd
		X := ((X2 := X1 + (W1 - W2)//2) + W2 &gt; A_ScreenWidth) ? A_ScreenWidth - W2 : X2 &lt; 0 ? 0 : X2
		Y := ((Y2 := Y1 + H1 + 20) + H2 &lt; A_ScreenHeight) ? Y2 : (Y2 := Y1 - 20 - H2) &lt; 0 ? 0 : Y2
		Gui, Show, x%X% y%Y%, Настройки
		DetectHiddenWindows, Off
	} else {
		Gui, Show, % &quot;w&quot; Width-1 &quot; h&quot; Height+51, Настройки
	}
	return
}

2GuiEscape() {
	CancelSettingsFromGui()
}

2GuiClose() {
	CancelSettingsFromGui()
}

;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Конец окна Настроек ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

ScreenShotMenu() {
	ScreenShotMenuHandling()
}

SetInterpolationMode() {
	SetInterpolationModeFromMenu(A_ThisMenuItemPos)
}

ReloadScript() {
	SelectionHwnd := App.Selection.Hwnd
	if WinExist(&quot;ahk_id &quot; SelectionHwnd) {
		Reload
		return
	}

	Run, % &quot;&quot;&quot;&quot; A_AhkPath &quot;&quot;&quot; /restart &quot;&quot;&quot; A_ScriptFullPath &quot;&quot;&quot; HideSelection&quot;, % A_ScriptDir
	ExitApp
}

EditScript() {
	Edit
}

ShowHelp() {
	HideSettingsWindow()
	if !IsObject(App.Help)
		App.Help := {}

	HelpText := BuildHelpText()
	if WindowExistsByHwnd(App.Help.WindowHwnd) {
		GuiControl, 6:, % App.Help.TextHwnd, % HelpText
		Gui, 6:Show,, Справка
		return
	}

	Width := 520, TextHeight := 410, Margin := 14, ButtonWidth := 90
	TextWidth := Width - Margin * 2
	WindowHeight := TextHeight + 62

	Gui, 6:+LastFound +hwndHelpHwnd
	App.Help.WindowHwnd := HelpHwnd
	Gui, 6:Margin, %Margin%, %Margin%
	Gui, 6:Font, s10 q5, Segoe UI
	Gui, 6:Add, Edit, % &quot;ReadOnly +VScroll w&quot; TextWidth &quot; h&quot; TextHeight &quot; hwndHelpTextHwnd&quot;, % HelpText
	SetStaticColor(HelpTextHwnd, 0xFFFFFF, 0x000000)
	App.Help.TextHwnd := HelpTextHwnd
	Gui, 6:Add, Button, % &quot;gCloseHelpWindow Default x&quot; (Width - Margin - ButtonWidth) &quot; y+10 w&quot; ButtonWidth &quot; h24&quot;, OK
	Gui, 6:Show, % &quot;w&quot; Width &quot; h&quot; WindowHeight, Справка
	PostMessage, 0x00B1, 0, 0, , ahk_id %HelpTextHwnd%   ;  EM_SETSEL
	return
}

6GuiEscape() {
	HideHelpWindow()
}

6GuiClose() {
	HideHelpWindow()
}

CloseHelpWindow(CtrlHwnd := &quot;&quot;, GuiEvent := &quot;&quot;, EventInfo := &quot;&quot;, EventErrorLevel := &quot;&quot;) {
	HideHelpWindow()
}

HideHelpWindow() {
	if !IsObject(App.Help)
		return
	if WindowExistsByHwnd(App.Help.WindowHwnd)
		Gui, 6:Hide
}

HideSettingsWindow() {
	if WindowExistsByHwnd(App.Settings.HotkeysHwnd)
		Gui, 4:Hide
	if WindowExistsByHwnd(App.Settings.WindowHwnd) {
		Gui, 2:Hide
		ResumeScriptHotkeysAfterSettings()
	}
}

BuildHelpText() {
Text =
(
ScreenCatcher

ScreenCatcher делает снимки экрана, активного окна и выбранной области. Снимок можно сохранить в файл, скопировать в буфер обмена, открыть в предпросмотре или загрузить на Imgbb.

Возможности:
- снимок всего экрана, активного окна или выбранной области;
- выбор области рамкой или быстрым выделением мышью поверх экрана;
- точное позиционирование, центрирование и изменение размера окна выделения;
- предпросмотр снимков: окна можно перемещать, изменять в размере и открывать по ним контекстное меню;
- настройка формата файла, качества JPEG и папки сохранения;
- захват курсора, уведомления и запуск вместе с Windows;
- настройка цвета, прозрачности и маркеров окна выделения.
)
	Text .= &quot;`r`n`r`nГорячие клавиши:`r`n&quot; . BuildHelpHotkeysText()
	Text .= &quot;`r`n`r`nПодсказки:`r`n&quot;
	Text .= &quot;- Если горячая клавиша не назначена, действие можно вызвать из меню трея.`r`n&quot;
	Text .= &quot;- При быстром выделении мышью выберите нужную область; Esc или правая кнопка отменяют выбор.`r`n&quot;
	Text .= &quot;- Горячие клавиши можно изменить в настройках.&quot;
	return Text
}

BuildHelpHotkeysText() {
	Labels := ScreenCatcherHotkeyLabels()
	Text := &quot;&quot;
	for k, HotkeyName in ScreenCatcherHotkeyNames()
		Text .= Labels[HotkeyName] &quot;: &quot; HelpHotkeyText(App.Hotkeys[HotkeyName]) &quot;`r`n&quot;
	return RTrim(Text, &quot;`r`n&quot;)
}

HelpHotkeyText(HotkeyValue) {
	Text := Hotkey_HKToStr(HotkeyValue)
	return (Text = &quot;Нет&quot; || !IsHotkeyEnabled(HotkeyValue)) ? &quot;не назначена&quot; : Text
}

WindowExistsByHwnd(Hwnd) {
	if !Hwnd
		return false
	DetectHiddenWindowsWasOn := A_DetectHiddenWindows
	DetectHiddenWindows, On
	Exists := WinExist(&quot;ahk_id &quot; Hwnd)
	DetectHiddenWindows, %DetectHiddenWindowsWasOn%
	return Exists
}

ShowSettingsFromMenu(ItemName := &quot;&quot;, ItemPos := &quot;&quot;, MenuName := &quot;&quot;) {
	OpenSettingsWindow(SettingsTabFromMenu(MenuName, ItemPos))
}

SettingsTabFromMenu(MenuName, ItemPos) {
	if (MenuName = &quot;Tray&quot;)
		return ItemPos - 1
	return ItemPos + 1
}

StoreSelectionWindowHwnd() {
	WinGet, SelectionHwnd
	App.Selection.Hwnd := SelectionHwnd
}

ShutdownApplication() {
	SelectionHwnd := App.Selection.Hwnd

	Loop {
		if (MaxIndex := App.Windows.MaxIndex()) {
			Gui, %MaxIndex%:Destroy
			App.Windows.Remove(MaxIndex, &quot;&quot;)
			Sleep, 50
		}
	} until !MaxIndex

	if WinExist(&quot;ahk_id &quot; SelectionHwnd)
		Melt(SelectionHwnd, App.Config.ARGB&gt;&gt;24, 0)
	if IsObject(App.Selection.Overlay)
		App.Selection.Overlay.Dispose()

	RestoreCursors()
	GdipShutdown(App.GdipToken)

	SaveSettings()
	ExitApp
}

MenuHotkeyCaption(Text, HotkeyValue) {
	hk := Hotkey_HKToStr(HotkeyValue)
	return Text . (hk = &quot;Нет&quot; ? &quot;&quot; : A_Tab . hk)
}

UpdateMenuHotkeyCaptions() {
	SelectionHwnd := App.Selection.Hwnd

	NewTrayMenuHide := MenuHotkeyCaption(&quot;Скрыть окно выделения&quot;, App.Hotkeys.ShowHideSelWindow)
	NewTrayMenuShow := MenuHotkeyCaption(&quot;Показать окно выделения&quot;, App.Hotkeys.ShowHideSelWindow)
	NewTrayMenuCenter := MenuHotkeyCaption(&quot;Главное окно в центр экрана&quot;, App.Hotkeys.SelWindowToCenter)
	NewTrayMenuOpenOutDir := MenuHotkeyCaption(&quot;Открыть папку вывода&quot;, App.Hotkeys.OpenOutputFolder)
	NewMainMenuArea := MenuHotkeyCaption(&quot;Сделать скриншот&quot;, App.Hotkeys.GetArea)
	NewMainMenuHide := MenuHotkeyCaption(&quot;Скрыть окно выделения&quot;, App.Hotkeys.ShowHideSelWindow)
	NewMainMenuOpenOutDir := MenuHotkeyCaption(&quot;Открыть папку вывода&quot;, App.Hotkeys.OpenOutputFolder)

	if WinExist(&quot;ahk_id &quot; SelectionHwnd)
		Menu, Tray, Rename, % App.Menu.TrayHide, % NewTrayMenuHide
	else
		Menu, Tray, Rename, % App.Menu.TrayShow, % NewTrayMenuShow
	Menu, Tray, Rename, % App.Menu.TrayCenter, % NewTrayMenuCenter
	Menu, Tray, Rename, % App.Menu.TrayOpenOutDir, % NewTrayMenuOpenOutDir

	Menu, MainMenu, Rename, % App.Menu.MainArea, % NewMainMenuArea
	Menu, MainMenu, Rename, % App.Menu.MainHide, % NewMainMenuHide
	Menu, MainMenu, Rename, % App.Menu.MainOpenOutDir, % NewMainMenuOpenOutDir

	App.Menu.TrayHide := NewTrayMenuHide
	App.Menu.TrayShow := NewTrayMenuShow
	App.Menu.TrayCenter := NewTrayMenuCenter
	App.Menu.TrayOpenOutDir := NewTrayMenuOpenOutDir
	App.Menu.MainArea := NewMainMenuArea
	App.Menu.MainHide := NewMainMenuHide
	App.Menu.MainOpenOutDir := NewMainMenuOpenOutDir
}

SaveSettings() {
	S_FormatInteger := A_FormatInteger
	SetFormat, IntegerFast, H
					;  секция          ключ                значение
	IniWriteSect(&quot;Color-Transparent&quot;, &quot;ARGB&quot;             , App.Config.ARGB)

	SetFormat, IntegerFast, D
	IniWriteSect(&quot;Output_Settings&quot;  , &quot;Output&quot;           , App.Config.Output
								    , &quot;Extension&quot;        , App.Config.Ext
								    , &quot;JPEG_Quality&quot;     , App.Config.JPEGQuality
								    , &quot;Out_Directory&quot;    , App.Config.OutDirectory)

	IniWriteSect(&quot;Program_Settings&quot; , &quot;Capture_Cursor&quot;   , App.Config.CaptureCursor
									, &quot;Show_Preview&quot;     , App.Config.ShowPreview
									, &quot;Notify&quot;           , App.Config.Notify
									, &quot;Resize_Pointers&quot;  , App.Config.ResizePointers
									, &quot;InterpolationMode&quot;, App.Config.InterpolationMode)

	IniWriteSect(&quot;Hotkeys&quot;          , &quot;GetArea&quot;          , App.Hotkeys.GetArea
									, &quot;GetActiveWindow&quot;  , App.Hotkeys.GetActiveWindow
									, &quot;GetScreen&quot;        , App.Hotkeys.GetScreen
									, &quot;ShowHideSelWindow&quot;, App.Hotkeys.ShowHideSelWindow
									, &quot;SelWindowToCenter&quot;, App.Hotkeys.SelWindowToCenter
									, &quot;SelectPreview&quot;    , App.Hotkeys.SelectPreview
									, &quot;OpenOutputFolder&quot; , App.Hotkeys.OpenOutputFolder
									, &quot;SelectAreaOverlay&quot;, App.Hotkeys.SelectAreaOverlay
									, &quot;QuickSelectAreaOverlay&quot;, App.Hotkeys.QuickSelectAreaOverlay)
	SetFormat, IntegerFast, %S_FormatInteger%
}

SettingsStateVars() {
	return &quot;ARGB|Output|ext|JPEGQuality|OutDirectory|CaptureCursor|ShowPreview|Notify|&quot;
		 . &quot;RunOnWinStart|ResizePointers|GetArea|GetActiveWindow|GetScreen|&quot;
		 . &quot;ShowHideSelWindow|SelWindowToCenter|SelectPreview|OpenOutputFolder|&quot;
		 . &quot;SelectAreaOverlay|QuickSelectAreaOverlay&quot;
}

SavePreviousSettingsState(StateVars := &quot;&quot;) {
	if (StateVars = &quot;&quot;)
		StateVars := SettingsStateVars()
	App.Settings.Previous := {}
	Loop, parse, StateVars, |
		App.Settings.Previous[A_LoopField] := SettingsStateValue(A_LoopField)
}

RestorePreviousSettingsState(StateVars := &quot;&quot;) {
	SelectionHwnd := App.Selection.Hwnd

	Previous := App.Settings.Previous
	if !IsObject(Previous)
		return

	if (App.Config.ARGB != Previous.ARGB)
		ColorChange := App.Config.ARGB

	if WinExist(&quot;ahk_id &quot; SelectionHwnd)
		SetResizePointerTimer(&quot;Off&quot;)

	if (StateVars = &quot;&quot;)
		StateVars := SettingsStateVars()
	Loop, parse, StateVars, |
		SetSettingsStateValue(A_LoopField, Previous[A_LoopField])
	App.Config.ResizePointers := NormalizeResizePointersValue(App.Config.ResizePointers)
	SyncSettingsColorFromARGB()
	SetHotkeyControlsFromVars()

	if WinExist(&quot;ahk_id &quot; SelectionHwnd)
		SetResizePointerTimer(50)

	if ColorChange {
		if WinExist(&quot;ahk_id &quot; SelectionHwnd)
			Melt(SelectionHwnd, ColorChange&gt;&gt;24, 0), PrevExist := 1
		Gui, 1:Color, % Format(&quot;{:X}&quot;, App.Config.ARGB &amp; 0xFFFFFF)
		UpdateMarkerColor()
		if PrevExist
			PrevExist := &quot;&quot;, Melt(SelectionHwnd, App.Config.ARGB&gt;&gt;24, 1)
		ColorChange := &quot;&quot;
	}
}

SyncSettingsColorFromARGB() {
	if !IsObject(App.Settings.Color)
		App.Settings.Color := {}
	App.Settings.Color.Red := (App.Config.ARGB &amp; 0xFF0000)&gt;&gt;16
	App.Settings.Color.Green := (App.Config.ARGB &amp; 0xFF00)&gt;&gt;8
	App.Settings.Color.Blue := App.Config.ARGB &amp; 0xFF
	App.Settings.Color.Trans := App.Config.ARGB&gt;&gt;24
}

SettingsStateValue(Name) {
	for index, HotkeyName in ScreenCatcherHotkeyNames()
		if (Name = HotkeyName)
			return App.Hotkeys[HotkeyName]

	for index, ConfigName in SettingsConfigNames()
		if (Name = ConfigName)
			return App.Config[ConfigName]

	if (Name = &quot;ResizePointers&quot;)
		return NormalizeResizePointersValue(App.Config.ResizePointers)
	if (Name = &quot;InterpolationMode&quot;)
		return App.Config.InterpolationMode
	return &quot;&quot;
}

SetSettingsStateValue(Name, Value) {
	for index, HotkeyName in ScreenCatcherHotkeyNames() {
		if (Name = HotkeyName) {
			App.Hotkeys[HotkeyName] := Value
			return
		}
	}

	for index, ConfigName in SettingsConfigNames() {
		if (Name = ConfigName) {
			App.Config[ConfigName] := Value
			return
		}
	}

	if (Name = &quot;ResizePointers&quot;)
		App.Config.ResizePointers := NormalizeResizePointersValue(Value)
	else if (Name = &quot;InterpolationMode&quot;)
		App.Config.InterpolationMode := Value
}

SettingsConfigNames() {
	return [ &quot;ARGB&quot;, &quot;Output&quot;, &quot;ext&quot;, &quot;JPEGQuality&quot;, &quot;OutDirectory&quot;
		   , &quot;CaptureCursor&quot;, &quot;ShowPreview&quot;, &quot;Notify&quot;, &quot;RunOnWinStart&quot; ]
}

CloseSettingsWindow() {
	if IsObject(App.Settings.MessageHandlers) {
		OnMessage( 0x111, App.Settings.MessageHandlers.WM_COMMAND, 0 )
		OnMessage( 0x20, App.Settings.MessageHandlers.WM_SETCURSOR, 0 )
		App.Settings.MessageHandlers := &quot;&quot;
	}
	Gui, 2:Destroy
	ResumeScriptHotkeysAfterSettings()
}

SuspendScriptHotkeysForSettings() {
	if App.Settings.ScriptHotkeysSuspended
		return
	SetScriptHotkeys(0)
	App.Settings.ScriptHotkeysSuspended := true
}

ResumeScriptHotkeysAfterSettings() {
	if !App.Settings.ScriptHotkeysSuspended
		return
	App.Settings.ScriptHotkeysSuspended := false
	SetScriptHotkeys(1)
}

ApplySettingsFromGui(CtrlHwnd := &quot;&quot;, GuiEvent := &quot;&quot;, EventInfo := &quot;&quot;, EventErrorLevel := &quot;&quot;) {
	if !ApplyHotkeySettings()
		return false
	ApplyProgramSettings()
	CheckImgbbClientIdOnApply()
	SaveSettings()
	SavePreviousSettingsState()
	return true
}

CheckImgbbClientIdOnApply() {
	if !InStr(App.Config.Output, &quot;Imgbb&quot;)
		return

	EnsureImgbbClientId()
}

EnsureImgbbClientId() {
	IniPath := IniFilePath()
	MissingValue := Chr(1) . &quot;__ScreenCatcherMissing__&quot;
	IniRead, ClientID, % IniPath, Imgbb, ClientID, % MissingValue
	if (ClientID = MissingValue) {
		EmptyValue := &quot;&quot;
		IniWrite, % EmptyValue, % IniPath, Imgbb, ClientID
		ClientID := &quot;&quot;
	}

	if (Trim(ClientID) != &quot;&quot;)
		return ClientID

	Message := &quot;Для отправки скриншотов на Imgbb нужен API key.`n`n&quot;
		. &quot;Зарегистрируйтесь: https://imgbb.com/`n&quot;
		. &quot;Получите ключ: https://api.imgbb.com/`n`n&quot;
		. &quot;Впишите ключ в файл ScreenCatcher.ini:`n&quot;
		. &quot;[Imgbb]`nClientID=...&quot;
	SetOwnDialogsForOpenWindows()
	MsgBox, 48, Нужен ключ Imgbb, %Message%
	Run, % &quot;notepad.exe &quot;&quot;&quot; IniPath &quot;&quot;&quot;&quot;
	return &quot;&quot;
}

SetOwnDialogsForOpenWindows() {
	if WindowExistsByHwnd(App.Selection.Hwnd)
		Gui, 1:+OwnDialogs
	if WindowExistsByHwnd(App.Settings.WindowHwnd)
		Gui, 2:+OwnDialogs
	if WindowExistsByHwnd(App.Settings.HotkeysHwnd)
		Gui, 4:+OwnDialogs
	if WindowExistsByHwnd(App.Menu.HighlightHwnd) || WinExist(&quot;Imgbb.com ahk_class AutoHotkeyGUI&quot;)
		Gui, 5:+OwnDialogs
	if IsObject(App.Help) &amp;&amp; WindowExistsByHwnd(App.Help.WindowHwnd)
		Gui, 6:+OwnDialogs
	if IsObject(App.Selection.Overlay) &amp;&amp; IsObject(App.Selection.Overlay.overlay)
			&amp;&amp; WindowExistsByHwnd(App.Selection.Overlay.overlay.hwnd)
		Gui, SelectionOverlay:+OwnDialogs

	for GuiNumber, WindowData in App.Windows {
		if IsObject(WindowData) &amp;&amp; WindowExistsByHwnd(WindowData.Hwnd)
			Gui, %GuiNumber%:+OwnDialogs
	}
}

ConfirmSettingsFromGui(CtrlHwnd := &quot;&quot;, GuiEvent := &quot;&quot;, EventInfo := &quot;&quot;, EventErrorLevel := &quot;&quot;) {
	if !ApplySettingsFromGui()
		return false
	CloseSettingsWindow()
	return true
}

CancelSettingsFromGui(CtrlHwnd := &quot;&quot;, GuiEvent := &quot;&quot;, EventInfo := &quot;&quot;, EventErrorLevel := &quot;&quot;) {
	RestorePreviousSettingsState()
	CloseSettingsWindow()
}

ScreenCatcherHotkeyNames() {
	return [&quot;GetArea&quot;, &quot;GetActiveWindow&quot;, &quot;GetScreen&quot;, &quot;ShowHideSelWindow&quot;
		  , &quot;SelWindowToCenter&quot;, &quot;SelectPreview&quot;, &quot;OpenOutputFolder&quot;
		  , &quot;SelectAreaOverlay&quot;, &quot;QuickSelectAreaOverlay&quot;]
}

ScreenCatcherHotkeyLabels() {
	return { GetArea:           &quot;Скриншот под окном выделения&quot;
		   , GetActiveWindow:   &quot;Скриншот активного окна&quot;
		   , GetScreen:         &quot;Скриншот всего экрана&quot;
		   , ShowHideSelWindow: &quot;Скрыть/показать окно выделения&quot;
		   , SelWindowToCenter: &quot;Окно выделения в центр экрана&quot;
		   , SelectPreview:     &quot;Выделить превью скриншота&quot;
		   , OpenOutputFolder:  &quot;Открыть папку вывода&quot;
		   , SelectAreaOverlay: &quot;Начать выделение мышью&quot;
		   , QuickSelectAreaOverlay: &quot;Быстро выделить область мышью&quot; }
}

ScreenCatcherHotkeyIndex(Name) {
	for k, v in ScreenCatcherHotkeyNames() {
		if (v = Name)
			return k
	}
	return 0
}

GetScriptHotkeys() {
	return { GetArea: App.Hotkeys.GetArea
		   , GetActiveWindow: App.Hotkeys.GetActiveWindow
		   , GetScreen: App.Hotkeys.GetScreen
		   , ShowHideSelWindow: App.Hotkeys.ShowHideSelWindow
		   , SelWindowToCenter: App.Hotkeys.SelWindowToCenter
		   , SelectPreview: App.Hotkeys.SelectPreview
		   , OpenOutputFolder: App.Hotkeys.OpenOutputFolder
		   , SelectAreaOverlay: App.Hotkeys.SelectAreaOverlay
		   , QuickSelectAreaOverlay: App.Hotkeys.QuickSelectAreaOverlay }
}

SetHotkeyControlsFromVars() {
	for k, name in ScreenCatcherHotkeyNames()
		Hotkey_Set(name, App.Hotkeys[name])
}

ApplyHotkeySettings() {
	if (Duplicate := FindDuplicateHotkey()) {
		ShowDuplicateHotkey(Duplicate)
		return false
	}

	oldHotkeys := GetScriptHotkeys()
	for k, name in ScreenCatcherHotkeyNames()
		App.Hotkeys[name] := Hotkey_Value(name)

	if !ScriptHotkeysEqual(oldHotkeys, GetScriptHotkeys()) {
		SetScriptHotkeys(0, oldHotkeys)
		if !App.Settings.ScriptHotkeysSuspended
			SetScriptHotkeys(1)
		UpdateMenuHotkeyCaptions()
	}
	return true
}

ScriptHotkeysEqual(a, b) {
	for k, v in a
		if (v != b[k])
			return false
	return true
}

IsHotkeyEnabled(HotkeyValue) {
	return Trim(HotkeyValue) != &quot;&quot;
}

UpdateHotkeysTabVisibility(CtrlHwnd := &quot;&quot;, GuiEvent := &quot;&quot;, EventInfo := &quot;&quot;, EventErrorLevel := &quot;&quot;) {
	ControlGet, NTab, Tab,,, % &quot;ahk_id &quot; App.Settings.TabHwnd
	if NTab = 4
		Gui, 4:Show, NA
	else
		Gui, 4:Show, Hide
}

UpdateSelectionColorFromGui(CtrlHwnd := &quot;&quot;, GuiEvent := &quot;&quot;, EventInfo := &quot;&quot;, EventErrorLevel := &quot;&quot;) {
	ReadColorControls()
	UpdateSelectionColor()
	UpdateColorValueLabels()
	UpdateMarkerColor()
}

ReadColorControls() {
	GuiControlGet, Red, 2:, % App.Settings.RedSliderHwnd
	GuiControlGet, Green, 2:, % App.Settings.GreenSliderHwnd
	GuiControlGet, Blue, 2:, % App.Settings.BlueSliderHwnd
	GuiControlGet, Trans, 2:, % App.Settings.TransSliderHwnd
	App.Settings.Color.Red := Red
	App.Settings.Color.Green := Green
	App.Settings.Color.Blue := Blue
	App.Settings.Color.Trans := Trans
}

UpdateColorValueLabels() {
	ColorState := App.Settings.Color
	GuiControl, 2:, % App.Settings.RedValueHwnd, % ColorState.Red
	GuiControl, 2:, % App.Settings.GreenValueHwnd, % ColorState.Green
	GuiControl, 2:, % App.Settings.BlueValueHwnd, % ColorState.Blue
	GuiControl, 2:, % App.Settings.TransValueHwnd, % ColorState.Trans
}

UpdateSelectionColor() {
	ColorState := App.Settings.Color
	ColorValue := (ColorState.Red&lt;&lt;16)|(ColorState.Green&lt;&lt;8)|ColorState.Blue
	App.Config.ARGB := (App.Config.ARGB &amp; 0xFF000000)|ColorValue
	SetFormat, IntegerFast, H
	Gui, 1:Color, % ColorValue
	SetFormat, IntegerFast, D
}

UpdateSelectionTransparencyFromGui(CtrlHwnd := &quot;&quot;, GuiEvent := &quot;&quot;, EventInfo := &quot;&quot;, EventErrorLevel := &quot;&quot;) {
	GuiControlGet, Trans, 2:, % App.Settings.TransSliderHwnd
	App.Settings.Color.Trans := Trans
	UpdateSelectionTransparency()
	UpdateColorValueLabels()
}

UpdateMarkerColor() {
	MarkerColor := (App.Config.ARGB &amp; 0xFFFFFF)^0xFFFFFF
	for index, MarkerHwnd in App.Selection.MarkerHwnds
		SetColorToControl(MarkerHwnd, MarkerColor)
}

UpdateSelectionTransparency() {
	SelectionHwnd := App.Selection.Hwnd

	Trans := App.Settings.Color.Trans
	WinSet, Transparent, % Trans, ahk_id %SelectionHwnd%
	App.Config.ARGB := Trans&lt;&lt;24|(App.Config.ARGB &amp; 0xFFFFFF)
}

UpdateOutputTargetFromGui(CtrlHwnd := &quot;&quot;, GuiEvent := &quot;&quot;, EventInfo := &quot;&quot;, EventErrorLevel := &quot;&quot;) {
	UpdateOutputTarget(OutputControlName(CtrlHwnd), CtrlHwnd)
}

OutputControlName(CtrlHwnd) {
	switch CtrlHwnd {
		case App.Settings.OutputFileHwnd  : return &quot;File&quot;
		case App.Settings.OutputClipHwnd  : return &quot;Clip&quot;
		case App.Settings.OutputImgbbHwnd : return &quot;Imgbb&quot;
		default: return &quot;&quot;
	}
}

UpdateOutputTarget(ControlName, CtrlHwnd := &quot;&quot;) {
	if (ControlName = &quot;&quot;)
		return
	GuiControlGet, OutVar, 2:, %CtrlHwnd%
	if OutVar
		App.Config.Output .= ControlName
	else {
		Output := App.Config.Output
		StringReplace, Output, Output, % ControlName,, 1
		App.Config.Output := Output
	}
}

SetOutputExtensionFromGui(CtrlHwnd := &quot;&quot;, GuiEvent := &quot;&quot;, EventInfo := &quot;&quot;, EventErrorLevel := &quot;&quot;) {
	SetOutputExtension(OutputExtensionFromHwnd(CtrlHwnd))
}

OutputExtensionFromHwnd(CtrlHwnd) {
	switch CtrlHwnd {
		case App.Settings.ExtensionPngHwnd: return &quot;png&quot;
		case App.Settings.ExtensionJpgHwnd: return &quot;jpg&quot;
		case App.Settings.ExtensionBmpHwnd: return &quot;bmp&quot;
		case App.Settings.ExtensionTifHwnd: return &quot;tif&quot;
		case App.Settings.ExtensionGifHwnd: return &quot;gif&quot;
		default: return &quot;&quot;
	}
}

SetOutputExtension(Extension) {
	if (Extension != &quot;&quot;)
		App.Config.Ext := Extension
}

SetJpegQuality() {
	GuiControlGet, JPEGQualityValue, 2:, % App.Settings.JpegQualitySliderHwnd
	App.Config.JPEGQuality := JPEGQualityValue
	GuiControl, 2:, % App.Settings.JpegQualityPercentHwnd, % App.Config.JPEGQuality &quot;`%&quot;
}

FocusSettingsSafeButton() {
	GuiControl, 2:Focus, Button17
}

ApplyProgramSettings() {
	SelectionHwnd := App.Selection.Hwnd

	OldResizePointers := App.Config.ResizePointers
	OldRunOnWinStart := App.Config.RunOnWinStart
	GuiControlGet, CaptureCursorChecked, 2:, % App.Settings.CaptureCursorHwnd
	App.Config.CaptureCursor := CaptureCursorChecked
	GuiControlGet, ShowPreviewChecked, 2:, % App.Settings.ShowPreviewHwnd
	App.Config.ShowPreview := ShowPreviewChecked
	GuiControlGet, NotifyChecked, 2:, % App.Settings.NotifyHwnd
	App.Config.Notify := NotifyChecked
	GuiControlGet, RunOnWinStartChecked, 2:, % App.Settings.RunOnWinStartHwnd
	App.Config.RunOnWinStart := RunOnWinStartChecked
	GuiControlGet, CursorsChecked, 2:, % App.Settings.ResizeCursorsHwnd
	App.Config.ResizePointers := CursorsChecked ? &quot;Cursors&quot; : &quot;Markers&quot;

	if (OldRunOnWinStart != App.Config.RunOnWinStart)
		SetRunOnWindowsStart(App.Config.RunOnWinStart)

	if (OldResizePointers != App.Config.ResizePointers &amp;&amp; WinExist(&quot;ahk_id &quot; SelectionHwnd)) {
		SetResizePointerTimer(&quot;Off&quot;, OldResizePointers)
		SetResizePointerTimer(50, App.Config.ResizePointers)
	}
}

SelectOutputDirectory() {
	SelectionHwnd := App.Selection.Hwnd

	Old_OutDirectory := App.Config.OutDirectory
	SelectionWasVisible := WinExist(&quot;ahk_id &quot; SelectionHwnd)
	if (SelectionWasVisible)
		ToggleSelectionWindow()
	Gui, 2:+OwnDialogs
	CurrentOutDirectory := App.Config.OutDirectory
	FileSelectFolder, OutDir, *%CurrentOutDirectory%, 3, Выберите папку для вывода:
	App.Config.OutDirectory := OutDir ? OutDir : App.Config.OutDirectory
	if (App.Config.OutDirectory != Old_OutDirectory)
		GuiControl, 2:, % App.Settings.OutDirectoryEditHwnd, % App.Config.OutDirectory
	if (SelectionWasVisible &amp;&amp; !WinExist(&quot;ahk_id &quot; SelectionHwnd))
		ToggleSelectionWindow()
}

IsRunOnWindowsStartEnabled() {
	FileGetShortcut, % GetRunOnWindowsStartShortcutPath(), OutTarget,, OutArgs
	return (OutTarget = A_ScriptFullPath &amp;&amp; OutArgs = 1)
}

SetRunOnWindowsStart(Enable) {
	ShortcutPath := GetRunOnWindowsStartShortcutPath()
	if (Enable)
		FileCreateShortcut, % A_ScriptFullPath, % ShortcutPath, % A_ScriptDir, 1
	else
		FileDelete, % ShortcutPath
}

GetRunOnWindowsStartShortcutPath() {
	return A_Startup &quot;\&quot; RegExReplace(A_ScriptName, &quot;(.*)\..*&quot;, &quot;$1.lnk&quot;)
}

OpenOutputDirectory() {
	Run, % App.Config.OutDirectory
}

UpdateResizePointerHover(Mode) {
	if (Mode = &quot;Cursors&quot;)
		App.Selection.ResizeHandle := Cursors1()
	else
		App.Selection.ResizeHandle := Markers1()
}

GuiSize() {
	MoveSelectionResizeMarkers(A_GuiWidth, A_GuiHeight)
}

MoveSelectionResizeMarkers(Width, Height) {
	GuiControl, Move, Static1, % &quot;x&quot; Width - 10 &quot;y&quot; Height - 10
	GuiControl, Move, Static2, % &quot;x0 y&quot; Height - 10
	GuiControl, Move, Static3, % &quot;x0 y0&quot;
	GuiControl, Move, Static4, % &quot;x&quot; Width - 10 &quot;y0&quot;
	GuiControl, Move, Static5, % &quot;x&quot; Width - 10 &quot;y&quot; (Height - 10)/2
	GuiControl, Move, Static6, % &quot;x&quot; (Width - 10)/2 &quot;y&quot; Height - 10
	GuiControl, Move, Static7, % &quot;x0 y&quot; (Height - 10)/2
	GuiControl, Move, Static8, % &quot;x&quot; (Width - 10)/2 &quot;y0&quot;
}

GuiContextMenu() {
	RestoreCursors()
	Menu, MainMenu, Show
}

; Окно выделения в центр экрана
CenterSelectionWindow() {
	SelectionHwnd := App.Selection.Hwnd

	if WinExist(&quot;ahk_id &quot; SelectionHwnd)
		Melt(SelectionHwnd, App.Config.ARGB&gt;&gt;24, 0)
	else {
		Menu, Tray, Rename, % App.Menu.TrayShow, % App.Menu.TrayHide
		SetResizePointerTimer(50)
	}
	Gui, 1:Show, % &quot;x&quot; (A_ScreenWidth - 200)//2 &quot; y&quot; (A_ScreenHeight - 200)//2 &quot; h200 w200 Hide&quot;
	Melt(SelectionHwnd, App.Config.ARGB&gt;&gt;24, 1)
}

GuiClose() {
	ToggleSelectionWindow()
}

GuiEscape() {
	ToggleSelectionWindow()
}

HideShow() {
	ToggleSelectionWindow()
}

ToggleSelectionWindow() {
	SelectionHwnd := App.Selection.Hwnd

	Exist := WinExist(&quot;ahk_id &quot; SelectionHwnd)
	Melt(SelectionHwnd, App.Config.ARGB&gt;&gt;24, !Exist)
	Menu, Tray, Rename, % Exist ? App.Menu.TrayHide : App.Menu.TrayShow
							, % Exist ? App.Menu.TrayShow : App.Menu.TrayHide
	SetResizePointerTimer(Exist ? &quot;Off&quot; : 50)
	( Exist &amp;&amp; NormalizeResizePointersValue(App.Config.ResizePointers) = &quot;Cursors&quot; &amp;&amp; RestoreCursors() )
}

CaptureAreaScreenshot() {
	CoordMode, Pixel, Screen
	MouseGetPos, mx, my
	prev_color := 0
	Loop {
		PixelGetColor, cur_color, %mx%, %my%
		if (cur_color = prev_color)
			break
		prev_color := cur_color
		Sleep, 30
	}
	GetScreenshot(&quot;Area&quot;)
}

SetInterpolationModeFromMenu(MenuItemPos) {
	Menu, Interpolation, UnCheck, % App.Menu.InterpolationItems[App.Config.InterpolationMode]
	App.Config.InterpolationMode := MenuItemPos
	Menu, Interpolation, Check, % App.Menu.InterpolationItems[App.Config.InterpolationMode]
}

FindDuplicateHotkey() {
	Names := ScreenCatcherHotkeyNames()
	Loop % Names.MaxIndex() {
		Name1 := Names[A_Index]
		Value1 := Hotkey_Value(Name1)
		if !IsHotkeyEnabled(Value1)
			continue
		i := A_Index + 1
		Loop % Names.MaxIndex() - A_Index {
			Name2 := Names[i]
			Value2 := Hotkey_Value(Name2)
			if (IsHotkeyEnabled(Value2) &amp;&amp; Hotkey_Equal(Value1, Value2))
				return {Name: Name2, OtherName: Name1, Hwnd: Hotkey_ID(Name2), Value: Value2}
			i++
		}
	}
	return &quot;&quot;
}

ShowDuplicateHotkey(Duplicate) {
	Labels := ScreenCatcherHotkeyLabels()
	ActivateHotkeyControl(Duplicate.Name)
	Hwnd := Duplicate.Hwnd
	WinGetPos, X, Y, W, H, ahk_id %Hwnd%
	Text := &quot;Эта горячая клавиша уже назначена:`n&quot; Labels[Duplicate.OtherName]
	new ToolTip({ text: Text, x: X, y: Y + H + 8, TimeOut: -3500
				, BackColor: 0x0055aa, TextColor: &quot;white&quot;, FontSize: 10, FontName: &quot;calibri&quot; })
	BlinkWindow(Hwnd)
}

ActivateHotkeyControl(Name) {
	Index := ScreenCatcherHotkeyIndex(Name)
	if !Index
		return
	SubTab := Index &lt; 4 ? 1 : Index &lt; 7 ? 2 : 3
	GuiControl, 2:Choose, % App.Settings.TabHwnd, 4
	Gui, 4:Show, NA
	GuiControl, 4:Choose, % App.Settings.HotkeysSubTabHwnd, %SubTab%
	Hwnd := Hotkey_ID(Name)
	ControlFocus,, ahk_id %Hwnd%
	Sleep, 50
}

BlinkWindow(Hwnd) {
	Loop 6 {
		DllCall(&quot;ShowWindowAsync&quot;, &quot;Ptr&quot;, Hwnd, &quot;Int&quot;, Mod(A_Index, 2) ? 0 : 4)
		Sleep, 70
	}
	DllCall(&quot;ShowWindowAsync&quot;, &quot;Ptr&quot;, Hwnd, &quot;Int&quot;, 4)
}

SetScriptHotkeys(Enable, Values := &quot;&quot;) {
	if !IsObject(Values)
		Values := GetScriptHotkeys()

	Hotkey, IfWinActive
	if IsHotkeyEnabled(Values.GetArea) {
		if Enable {
			obj := Func(&quot;GetScreenshot&quot;).Bind(&quot;Area&quot;)
			Hotkey, % Values.GetArea, % obj, On
		} else {
			Hotkey, % Values.GetArea, Off
		}
	}
	if IsHotkeyEnabled(Values.GetActiveWindow) {
		if Enable {
			obj := Func(&quot;GetScreenshot&quot;).Bind(&quot;ActiveWindow&quot;)
			Hotkey, % Values.GetActiveWindow, % obj, On
		} else {
			Hotkey, % Values.GetActiveWindow, Off
		}
	}
	if IsHotkeyEnabled(Values.GetScreen) {
		if Enable {
			obj := Func(&quot;GetScreenshot&quot;).Bind(&quot;Screen&quot;)
			Hotkey, % Values.GetScreen, % obj, On
		} else {
			Hotkey, % Values.GetScreen, Off
		}
	}
	if IsHotkeyEnabled(Values.ShowHideSelWindow) {
		if Enable
			Hotkey, % Values.ShowHideSelWindow, HideShow, On
		else
			Hotkey, % Values.ShowHideSelWindow, Off
	}
	if IsHotkeyEnabled(Values.SelWindowToCenter) {
		if Enable
			Hotkey, % Values.SelWindowToCenter, CenterSelectionWindow, On
		else
			Hotkey, % Values.SelWindowToCenter, Off
	}
	if IsHotkeyEnabled(Values.OpenOutputFolder) {
		if Enable
			Hotkey, % Values.OpenOutputFolder, OpenOutputDirectory, On
		else
			Hotkey, % Values.OpenOutputFolder, Off
	}
	if IsHotkeyEnabled(Values.SelectAreaOverlay) {
		if Enable {
			obj := Func(&quot;StartOverlaySelection&quot;).Bind(&quot;Arm&quot;)
			Hotkey, % Values.SelectAreaOverlay, % obj, On
		} else {
			Hotkey, % Values.SelectAreaOverlay, Off
		}
	}
	if IsHotkeyEnabled(Values.QuickSelectAreaOverlay) {
		if Enable {
			obj := Func(&quot;StartOverlaySelection&quot;).Bind(&quot;Quick&quot;)
			Hotkey, % Values.QuickSelectAreaOverlay, % obj, On
		} else {
			Hotkey, % Values.QuickSelectAreaOverlay, Off
		}
	}

	if IsHotkeyEnabled(Values.SelectPreview) {
		Hotkey, if, WinExist(&quot;ScreenShot ahk_class AutoHotkeyGUI&quot;)
		if Enable
			Hotkey, % Values.SelectPreview, FindHighlight, On
		else
			Hotkey, % Values.SelectPreview, Off
	}
	Hotkey, if
}

SetResizeHotkeyState(State) {
	Hotkey, IfWinActive
	Hotkey, LButton, WinResize, %State%
	Hotkey, if
}

Cursors1() {
	static
	SelectionHwnd := App.Selection.Hwnd
	S = 10
	MouseGetPos, X_M, Y_M, ID_Win
	for index, value in App.Windows {
		if (value.Hwnd = ID_Win) {
			if Cursor {
				RestoreCursors()
				SetResizeHotkeyState(&quot;Off&quot;)
				Cursor := &quot;&quot;
			}
			return
		}
	}
	WinGetPos, X, Y, W, H, ahk_id %SelectionHwnd%
	XW := X + W, YH := Y + H
	if ((X_M &lt;= XW) &amp;&amp; (X_M &gt; XW - 2*S) &amp;&amp; (Y_M &lt;= YH) &amp;&amp; (Y_M &gt; YH - S))
					|| ((X_M &lt;= XW) &amp;&amp; (X_M &gt; XW - S) &amp;&amp; (Y_M &lt;= YH) &amp;&amp; (Y_M &gt; YH - 2*S))
		Var := 1

	else if ((X_M &lt; X + 2*S) &amp;&amp; (X_M &gt;= X) &amp;&amp; (Y_M &lt;= YH) &amp;&amp; (Y_M &gt; YH - S))
					|| ((X_M &lt; X + S) &amp;&amp; (X_M &gt;= X) &amp;&amp; (Y_M &lt;= YH) &amp;&amp; (Y_M &gt; YH - 2*S))
		Var := 2

	else if ((X_M &lt; X + 2*S) &amp;&amp; (X_M &gt;= X) &amp;&amp; (Y_M &lt; Y + S) &amp;&amp; (Y_M &gt;= Y))
					|| ((X_M &lt; X + S) &amp;&amp; (X_M &gt;= X) &amp;&amp; (Y_M &lt; Y + 2*S) &amp;&amp; (Y_M &gt;= Y))
		Var := 3

	else if ((X_M &lt;= XW ) &amp;&amp; (X_M &gt; XW - 2*S) &amp;&amp; (Y_M &lt; Y + S) &amp;&amp; (Y_M &gt;= Y))
					|| ((X_M &lt;= XW ) &amp;&amp; (X_M &gt; XW - S) &amp;&amp; (Y_M &lt; Y + 2*S) &amp;&amp; (Y_M &gt;= Y))
		Var := 4

	else if (X_M &lt;= XW) &amp;&amp; (X_M &gt; XW - S) &amp;&amp; (Y_M &lt;= YH - S) &amp;&amp; (Y_M &gt;= Y + S)
		Var := 5
	else if (X_M &lt;= XW - S) &amp;&amp; (X_M &gt;= X + S) &amp;&amp; (Y_M &lt;= YH) &amp;&amp; (Y_M &gt; YH - S)
		Var := 6
	else if (X_M &lt; X + S) &amp;&amp; (X_M &gt;= X) &amp;&amp; (Y_M &lt;= YH - S) &amp;&amp; (Y_M &gt;= Y + S)
		Var := 7
	else if (X_M &lt;= XW - S) &amp;&amp; (X_M &gt;= X + S) &amp;&amp; (Y_M &lt; Y + S) &amp;&amp; (Y_M &gt;= Y)
		Var := 8
	else
		Var := &quot;&quot;

	if Var {
		SetResizeHotkeyState(&quot;On&quot;)
		if (Var = 1 || Var = 3) &amp;&amp; (Cursor != &quot;IDC_SIZENWSE&quot;)
			RestoreCursors(), SetSystemCursor(&quot;IDC_SIZENWSE&quot;), Cursor := &quot;IDC_SIZENWSE&quot;

		if (Var = 2 || Var = 4) &amp;&amp; (Cursor != &quot;IDC_SIZENESW&quot;)
			RestoreCursors(), SetSystemCursor(&quot;IDC_SIZENESW&quot;), Cursor := &quot;IDC_SIZENESW&quot;

		if (Var = 5 || Var = 7) &amp;&amp; (Cursor != &quot;IDC_SIZEWE&quot;)
			RestoreCursors(), SetSystemCursor(&quot;IDC_SIZEWE&quot;), Cursor := &quot;IDC_SIZEWE&quot;

		if (Var = 6 || Var = 8) &amp;&amp; (Cursor != &quot;IDC_SIZENS&quot;)
			RestoreCursors(), SetSystemCursor(&quot;IDC_SIZENS&quot;), Cursor := &quot;IDC_SIZENS&quot;
	} else {
		SetResizeHotkeyState(&quot;Off&quot;)
		RestoreCursors(), Cursor := &quot;&quot;
	}
	return Var
}

Markers1() {
	SelectionHwnd := App.Selection.Hwnd
	MouseGetPos, X_M, Y_M, ID_Win
	for index, value in App.Windows {
		if (value.Hwnd = ID_Win) {
			SetResizeHotkeyState(&quot;Off&quot;)
			Loop 8
				GuiControl, Hide, static%a_index%
			return
		}
	}
	WinGetPos, X, Y, W, H, ahk_id %SelectionHwnd%
	XW := X + W, YH := Y + H

	if (X_M &lt;= XW + 20) &amp;&amp; (X_M &gt;= XW - 10) &amp;&amp; (Y_M &lt;= YH + 20) &amp;&amp; (Y_M &gt;= YH - 10)
		Var := 1

	else if (X_M &lt;= X + 10) &amp;&amp; (X_M &gt;= X - 20) &amp;&amp; (Y_M &lt;= YH + 20) &amp;&amp; (Y_M &gt;= YH - 10)
		Var := 2

	else if (X_M &lt;= X + 10) &amp;&amp; (X_M &gt;= X - 20) &amp;&amp; (Y_M &lt;= Y + 10) &amp;&amp; (Y_M &gt;= Y - 20)
		Var := 3

	else if (X_M &lt;= XW + 20) &amp;&amp; (X_M &gt;= XW - 10) &amp;&amp; (Y_M &lt;= Y + 10) &amp;&amp; (Y_M &gt;= Y - 20)
		Var := 4

	else if (X_M &lt;= XW + 20) &amp;&amp; (X_M &gt;= XW - 10) &amp;&amp; (Y_M &lt; YH - 10) &amp;&amp; (Y_M &gt; Y + 10)
		Var := 5

	else if (X_M &lt; XW - 10) &amp;&amp; (X_M &gt; X + 10) &amp;&amp; (Y_M &lt;= YH + 20) &amp;&amp; (Y_M &gt;= YH - 10)
		Var := 6

	else if (X_M &lt;= X + 10) &amp;&amp; (X_M &gt;= X - 20) &amp;&amp; (Y_M &lt; YH - 10) &amp;&amp; (Y_M &gt; Y + 10)
		Var := 7

	else if (X_M &lt; XW - 10) &amp;&amp; (X_M &gt; X + 10) &amp;&amp; (Y_M &lt;= Y + 10) &amp;&amp; (Y_M &gt;= Y - 20)
		Var := 8

	else Var := &quot;&quot;

	if Var {
		SetResizeHotkeyState(&quot;On&quot;)
		GuiControl, Show, static%Var%
		Loop 8 {
			if (a_index != Var)
				GuiControl, Hide, static%a_index%
		}
	} else {
		SetResizeHotkeyState(&quot;Off&quot;)
		Loop 8 {
			GuiControl, Hide, static%a_index%
		}
	}
	return Var
}

WinResize() {
	SelectionHwnd := App.Selection.Hwnd
	ResizeVar := App.Selection.ResizeHandle
	SetResizePointerTimer(&quot;Off&quot;)
	MouseGetPos, X_M_Old, Y_M_Old
	WinGetPos, X, Y, W, H, ahk_id %SelectionHwnd%
	while GetKeyState(&quot;LButton&quot;, &quot;P&quot;) {
		MouseGetPos, X_M, Y_M
		dX := X_M - X_M_Old, dY := Y_M - Y_M_Old
		IfEqual, ResizeVar, 1, WinMove, ahk_id %SelectionHwnd%,,,, W + dX, H + dY
		IfEqual, ResizeVar, 2, WinMove, ahk_id %SelectionHwnd%,, X + dX,, W - dX, H + dY
		IfEqual, ResizeVar, 3, WinMove, ahk_id %SelectionHwnd%,, X + dX, Y + dY, W - dX, H - dY
		IfEqual, ResizeVar, 4, WinMove, ahk_id %SelectionHwnd%,,, Y + dY, W + dX, H - dY
		IfEqual, ResizeVar, 5, WinMove, ahk_id %SelectionHwnd%,,,, W + dX
		IfEqual, ResizeVar, 6, WinMove, ahk_id %SelectionHwnd%,,,,, H + dY
		IfEqual, ResizeVar, 7, WinMove, ahk_id %SelectionHwnd%,, X + dX,, W - dX
		IfEqual, ResizeVar, 8, WinMove, ahk_id %SelectionHwnd%,,, Y + dY,, H - dY
		Sleep, 10
	}
	SetResizePointerTimer(&quot;On&quot;)
}

IsSelectionWindowActive() {
	SelectionHwnd := App.Selection.Hwnd
	return (SelectionHwnd &amp;&amp; WinActive(&quot;ahk_id &quot; SelectionHwnd))
}

NudgeSelectionEdge(Edge, Delta) {
	static MinSize := 1

	SelectionHwnd := App.Selection.Hwnd
	if !WinExist(&quot;ahk_id &quot; SelectionHwnd)
		return

	WinGetPos, X, Y, W, H, ahk_id %SelectionHwnd%
	if (Edge = &quot;Left&quot;) {
		if (W - Delta &lt; MinSize)
			return
		WinMove, ahk_id %SelectionHwnd%,, X + Delta, Y, W - Delta, H
	} else if (Edge = &quot;Top&quot;) {
		if (H - Delta &lt; MinSize)
			return
		WinMove, ahk_id %SelectionHwnd%,, X, Y + Delta, W, H - Delta
	} else if (Edge = &quot;Right&quot;) {
		if (W + Delta &lt; MinSize)
			return
		WinMove, ahk_id %SelectionHwnd%,, X, Y, W + Delta, H
	} else if (Edge = &quot;Bottom&quot;) {
		if (H + Delta &lt; MinSize)
			return
		WinMove, ahk_id %SelectionHwnd%,, X, Y, W, H + Delta
	}
}

NormalizeLoadedSettings() {
	if !IsIntegerSetting(App.Config.ARGB)
		App.Config.ARGB := 0x82FFC700

	if !RegExMatch(App.Config.Output, &quot;^(File|Clip|Imgbb)+$&quot;)
		App.Config.Output := &quot;File&quot;

	if !RegExMatch(App.Config.Ext, &quot;^(png|jpg|bmp|tif|gif)$&quot;)
		App.Config.Ext := &quot;png&quot;

	if (!IsIntegerSetting(App.Config.JPEGQuality) || App.Config.JPEGQuality &lt; 50 || App.Config.JPEGQuality &gt; 100)
		App.Config.JPEGQuality := 100

	if (App.Config.OutDirectory = &quot;&quot; || App.Config.OutDirectory = &quot;OutDirectory&quot;)
		App.Config.OutDirectory := A_ScriptDir

	if !IsBooleanSetting(App.Config.CaptureCursor)
		App.Config.CaptureCursor := 0
	if !IsBooleanSetting(App.Config.ShowPreview)
		App.Config.ShowPreview := 1
	if !IsBooleanSetting(App.Config.Notify)
		App.Config.Notify := 1

	App.Config.ResizePointers := NormalizeResizePointersValue(App.Config.ResizePointers)

	if (!IsIntegerSetting(App.Config.InterpolationMode) || App.Config.InterpolationMode &lt; 1 || App.Config.InterpolationMode &gt; 8)
		App.Config.InterpolationMode := 3
}

NormalizeResizePointersValue(Value) {
	return Value = &quot;Markers&quot; ? &quot;Markers&quot; : &quot;Cursors&quot;
}

SetResizePointerTimer(State, Value := &quot;&quot;) {
	TimerName := NormalizeResizePointersValue(Value = &quot;&quot; ? App.Config.ResizePointers : Value)
	SetTimer, %TimerName%, %State%
}

Cursors() {
	UpdateResizePointerHover(A_ThisFunc)
}

Markers() {
	UpdateResizePointerHover(A_ThisFunc)
}

IsIntegerSetting(Value) {
	return RegExMatch(Value, &quot;i)^\s*(?:0x[0-9a-f]+|\d+)\s*$&quot;)
}

IsBooleanSetting(Value) {
	return Value = 0 || Value = 1
}

IniReadConfigSect(Section, params*) {
; Читает секцию Section ini-файла в App.Config
	ConfigNames := [ &quot;ARGB&quot;, &quot;Output&quot;, &quot;Ext&quot;, &quot;JPEGQuality&quot;, &quot;OutDirectory&quot;, &quot;CaptureCursor&quot;
		           , &quot;ShowPreview&quot;, &quot;Notify&quot;, &quot;ResizePointers&quot;, &quot;InterpolationMode&quot; ]
	IniReadObjectSect(Section, App.Config, ConfigNames, params*)
}

IniReadHotkeysSect(Section, params*) {
; Читает секцию Section ini-файла в App.Hotkeys.
	if !IsObject(App.Hotkeys)
		App.Hotkeys := {}
	IniReadObjectSect(Section, App.Hotkeys, ScreenCatcherHotkeyNames(), params*)
}

IniReadObjectSect(Section, Target, AllowedNames, params*) {
; Читает секцию Section ini-файла в объект Target.
	IniName := IniFilePath()
	for index, param in params {
		if Mod(index, 3) = 1
			Key := param
		else if Mod(index, 3) = 2
			Name := param
		else {
			IniRead, Value, % IniName, % Section, % Key, % param
			SetObjectValue(Target, AllowedNames, Name, Value)
		}
	}
}

SetObjectValue(Target, AllowedNames, Name, Value) {
	for index, AllowedName in AllowedNames {
		if (Name = AllowedName) {
			Target[AllowedName] := Value
			return
		}
	}
}

IniWriteSect(Section, params*) {
; Пишет секцию Section ini-файла в директории скрипта.
    IniName := IniFilePath()
    for index, param in params {
		if Mod(index, 2)
			Key := param
		else
			IniWrite, % param, % IniName, % Section, % Key
	}
}

IniFilePath() {
	return RegExReplace(A_ScriptFullPath, &quot;(.*)\..*&quot;, &quot;$1.ini&quot;)
}

Melt(hWnd, Trans, Appear, N = 20) {
	WinShow, ahk_id %hWnd%
	while (i := A_Index * N) &lt; Trans {
		WinSet, Transparent, % Appear ? i : Trans - i, ahk_id %hWnd%
		Sleep, 10
	}
	if Appear
		WinSet, Transparent, % Trans, ahk_id %hWnd%
	else
		WinHide, ahk_id %hWnd%
}

SetSystemCursor(IDC) {
	static IDC_SIZENWSE := 32642, IDC_SIZENESW := 32643, IDC_SIZEWE := 32644
		 , IDC_SIZENS   := 32645, IDC_ARROW    := 32512
		 , sys_cursors  := [32512,32513,32514,32516,32642,32643,32644,32645,32646,32648,32649,32650]

	hCursor := DllCall(&quot;LoadCursor&quot;, &quot;UInt&quot;, 0, &quot;Int&quot;, %IDC%)
	for k, v in sys_cursors {
		hCopy := DllCall(&quot;CopyImage&quot;, &quot;Ptr&quot;, hCursor, &quot;UInt&quot;, 2, &quot;Int&quot;, 0, &quot;Int&quot;, 0, &quot;UInt&quot;, 0)
		DllCall(&quot;SetSystemCursor&quot;, &quot;UInt&quot;, hCopy, &quot;Int&quot;, v)
	}
}

RestoreCursors() {
    DllCall(&quot;SystemParametersInfo&quot;, &quot;UInt&quot;, SPI_SETCURSORS:=0x57, &quot;UInt&quot;, 0, &quot;UInt&quot;, 0, &quot;UInt&quot;, 0)
}

SetColorToControl(hwnd, Color) {	; окрашивает static-контрол со стилем SS_BITMAP = 0xE в цвет Color
	WinGetPos,,, w, h, ahk_id %hwnd%

	DllCall(&quot;gdiplus\GdipCreateSolidFill&quot;, &quot;UInt&quot;, 0xFF000000|Color, &quot;Ptr*&quot;, pBrush)
	DllCall(&quot;gdiplus\GdipCreateBitmapFromScan0&quot;, &quot;Int&quot;, w, &quot;Int&quot;, h, &quot;Int&quot;, 0, &quot;Int&quot;, 0x26200A, &quot;Ptr&quot;, 0, &quot;Ptr*&quot;, pBitmap)
	DllCall(&quot;gdiplus\GdipGetImageGraphicsContext&quot;, &quot;Ptr&quot;, pBitmap, &quot;Ptr*&quot;, G)
	DllCall(&quot;gdiplus\GdipFillRectangle&quot;, &quot;Ptr&quot;, G, &quot;Ptr&quot;, pBrush, &quot;Float&quot;, 0, &quot;Float&quot;, 0, &quot;Float&quot;, w, &quot;Float&quot;, h)
	DllCall(&quot;gdiplus\GdipCreateHBITMAPFromBitmap&quot;, &quot;Ptr&quot;, pBitmap, &quot;Ptr*&quot;, hBitmap, &quot;Int&quot;, 0xFFFFFFFF)
	SendMessage, STM_SETIMAGE := 0x172,, hBitmap,, ahk_id %hwnd%
	DllCall(&quot;DeleteObject&quot;, &quot;Ptr&quot;, ErrorLevel)
	DllCall(&quot;gdiplus\GdipDeleteBrush&quot;, &quot;Ptr&quot;, pBrush)
	DllCall(&quot;gdiplus\GdipDeleteGraphics&quot;, &quot;Ptr&quot;, G)
	DllCall(&quot;gdiplus\GdipDisposeImage&quot;, &quot;Ptr&quot;, pBitmap)
	DllCall(&quot;DeleteObject&quot;, &quot;Ptr&quot;, hBitmap)
}

DDLCreate(N) {
	Loop % N
		String .= A_Index-1 &quot;|&quot;
	return SubStr(String, 1, -1)
}

WM_LBUTTONDOWN(wParam, lParam, msg, hwnd) {
	if IsOverlaySelectionHwnd(hwnd)
		return
	if (A_Gui = 1 || A_Gui &gt; 9) {
		if (A_Gui &gt; 9) {
			App.Menu.CancelPreviewIntroHwnd := hwnd
			HidePreviewHighlight()
		}
		PostMessage, WM_NCLBUTTONDOWN := 0xA1, HTCAPTION := 2,,, % &quot;ahk_id &quot; hwnd
	}

	else if (A_Gui = 2 &amp;&amp; WhichPageOfTab(App.Settings.TabHwnd) = 4)
	{
		MouseGetPos,,,, Control
		if InStr(Control, &quot;ComboBox&quot;)
		{
			GuiControlGet, OutputVar, 3:Enabled, % Control
			if OutputVar
				return
			else
				GuiControl, 3:Focus, TextT
		}
		else
			GuiControl, 3:Focus, TextT
	}
}

WM_LBUTTONDBLCLK(wParam, lParam, msg, hwnd) {
	if IsOverlaySelectionHwnd(hwnd)
		return
	(A_Gui = 1) &amp;&amp; GetScreenshot(&quot;Area&quot;)
}

WM_RBUTTONDOWN(wParam, lParam, msg, hwnd) {
	if IsOverlaySelectionHwnd(hwnd)
		return
	if (A_Gui &lt; 10 || A_Gui = &quot;Imgbb&quot;)
		return
	App.Menu.CurrentPreviewGui := A_Gui
	Menu, % App.Windows[A_Gui].Scale = 1 ? &quot;ScreenShotMenu&quot; : &quot;ScreenShotMenu2&quot;, Show
}

AHK_NOTIFYICON(wp, lp) {
	static WM_LBUTTONDOWN := 0x201
	(lp = WM_LBUTTONDOWN) &amp;&amp; ToggleSelectionWindow()
}

WhichPageOfTab(SettingsTabHwnd) {
	ControlGet, NTab, Tab,,, ahk_id %SettingsTabHwnd%
	return NTab
}

GetControlFocus(NGui) {
	GuiControlGet, Focus, %NGui%:Focus
	return Focus
}

GetOverlaySelection() {
	if !IsObject(App.Selection.Overlay) {
		gap := 5
		labelSpecs := [{style: {fontStyle: 1}
					  , provider: Func(&quot;OverlayTopLabelProvider&quot;).Bind(gap)}
					 , {style: {fontStyle: 1}
					  , provider: Func(&quot;OverlayBottomLabelProvider&quot;).Bind(gap)}]
		armedStyle := {fontStyle: 1, offsetX: 12, offsetY: 12}
		App.Selection.Overlay := new OverlaySelection(2, 0xFFAA00, labelSpecs, armedStyle)
		Gui, 1:Default
	}
	return App.Selection.Overlay
}

StartOverlaySelection(Mode) {
	Overlay := GetOverlaySelection()
	Callback := Func(&quot;CaptureOverlaySelection&quot;)
	if (Mode = &quot;Quick&quot;)
		Overlay.Quick(Callback)
	else
		Overlay.Arm(Callback)
}

CaptureOverlaySelection(Rect) {
	if (Rect.w &lt; 1 || Rect.h &lt; 1)
		return
	GetScreenshot(&quot;Area&quot;, Rect)
}

OverlayTopLabelProvider(Gap, Context) {
	Rect := Context.r
	return {text: Rect.x . &quot;, &quot; . Rect.y . &quot;  &quot; . Rect.w . &quot;x&quot; . Rect.h
		  , ax: Rect.x
		  , ay: Rect.y - Context.t - Round(Gap * Context.scale)
		  , side: &quot;top&quot;}
}

OverlayBottomLabelProvider(Gap, Context) {
	Rect := Context.r
	return {text: (Rect.x + Rect.w) . &quot;, &quot; . (Rect.y + Rect.h)
		  , ax: Rect.x + Rect.w
		  , ay: Rect.y + Rect.h + Context.t + Round(Gap * Context.scale)
		  , side: &quot;bottom&quot;}
}

IsOverlaySelectionState(State) {
	return IsObject(App.Selection.Overlay) &amp;&amp; App.Selection.Overlay.state = State
}

BeginOverlaySelectionDrag() {
	if IsObject(App.Selection.Overlay)
		App.Selection.Overlay.BeginDrag()
}

CancelOverlaySelection() {
	if IsObject(App.Selection.Overlay)
		App.Selection.Overlay.Cancel()
}

FinishOverlaySelection() {
	if IsObject(App.Selection.Overlay)
		App.Selection.Overlay.Finish()
}

GetScreenshot(Area, SelectionRect := &quot;&quot;) {
	SelectionHwnd := App.Selection.Hwnd
	SRCCOPY := 0x00CC0020, CAPTUREBLT := 0x40000000
	Critical
	if (Area = &quot;Area&quot;) {
		IfWinExist, ahk_id %SelectionHwnd%
		{
			Vis = 1
			WinHide	; Melt(SelectionHwnd, App.Config.ARGB&gt;&gt;24, 0, 50)
		}
		if IsObject(SelectionRect) {
			X := SelectionRect.x
			Y := SelectionRect.y
			W := SelectionRect.w
			H := SelectionRect.h
		} else {
			DetectHiddenWindows, On
			WinGetPos, X, Y, W, H, ahk_id %SelectionHwnd%
		}
		Rect := X &quot;|&quot; Y &quot;|&quot; W &quot;|&quot; H
		pBitmap := BitmapFromScreen(Rect, App.Config.CaptureCursor, SRCCOPY|CAPTUREBLT)
		if Vis
		{
			Sleep, 50
			WinShow	; Melt(SelectionHwnd, App.Config.ARGB&gt;&gt;24, 1, 50)
		}
	} else if (Area = &quot;ActiveWindow&quot;) {
		WinGet, hwnd, ID, A
		if !GetVisibleWindowRect(hwnd, X, Y, W, H)
			WinGetPos, X, Y, W, H, ahk_id %hwnd%
		pBitmap := BitmapFromScreen(X &quot;|&quot; Y &quot;|&quot; W &quot;|&quot; H, App.Config.CaptureCursor, SRCCOPY|CAPTUREBLT)
	} else {
		X := Y := 0
		W := A_ScreenWidth
		H := A_ScreenHeight
		pBitmap := BitmapFromScreen(0, App.Config.CaptureCursor, SRCCOPY|CAPTUREBLT)
	}
	Critical, Off   ; capture is done; let the message queue flow for preview/notification

	Time := A_Year &quot;.&quot; A_MM  &quot;.&quot; A_DD &quot;-&quot; A_Hour &quot;.&quot; A_Min &quot;.&quot; A_Sec &quot;.&quot; SubStr(A_MSec, 1, 1)

	if (App.Config.ShowPreview) {
		ShowPreview(pBitmap, Time, X, Y, W, H)
	} else {
		SaveScreenshot(pBitmap, Time)
	}
}

BitmapFromHWND(hwnd) {
	WinGetPos,,, Width, Height, ahk_id %hwnd%
	hbm := CreateDIBSection(Width, Height)
	hdc := DllCall(&quot;CreateCompatibleDC&quot;, &quot;Ptr&quot;, 0)
	obm := DllCall(&quot;SelectObject&quot;, &quot;Ptr&quot;, hdc, &quot;Ptr&quot;, hbm)
	DllCall(&quot;PrintWindow&quot;, &quot;Ptr&quot;, hwnd, &quot;Ptr&quot;, hdc, &quot;UInt&quot;, PW_RENDERFULLCONTENT := 2)
	DllCall(&quot;gdiplus\GdipCreateBitmapFromHBITMAP&quot;, &quot;Ptr&quot;, hbm, &quot;Ptr&quot;, 0, &quot;Ptr*&quot;, pBitmap)
	DllCall(&quot;SelectObject&quot;, &quot;Ptr&quot;, hdc, &quot;Ptr&quot;, obm)
	DllCall(&quot;DeleteObject&quot;, &quot;Ptr&quot;, hbm)
	DllCall(&quot;DeleteDC&quot;, &quot;Ptr&quot;, hdc)
	return pBitmap
}

GetBitmapFromWindow(hwnd) {
	WinGetPos,,, W, H, ahk_id %hwnd%
	hWDC := DllCall(&quot;GetWindowDC&quot;, &quot;Ptr&quot;, hwnd, &quot;Ptr&quot;)
	hdc := DllCall(&quot;CreateCompatibleDC&quot;, &quot;Ptr&quot;, hWDC, &quot;Ptr&quot;)
	hbm := DllCall(&quot;CreateCompatibleBitmap&quot;, &quot;Ptr&quot;, hWDC, &quot;Int&quot;, W, &quot;Int&quot;, H)
	obm := DllCall(&quot;SelectObject&quot;, &quot;Ptr&quot;, hdc, &quot;Ptr&quot;, hbm)
	DllCall(&quot;BitBlt&quot;, &quot;Ptr&quot;, hdc, &quot;Int&quot;, 0, &quot;Int&quot;, 0, &quot;Int&quot;, W, &quot;Int&quot;, H, &quot;Ptr&quot;, hWDC, &quot;Int&quot;, 0, &quot;Int&quot;, 0, &quot;UInt&quot;, 0x00CC0020)
	DllCall(&quot;ReleaseDC&quot;, &quot;Ptr&quot;, hwnd, &quot;Ptr&quot;, hWDC)
	DllCall(&quot;gdiplus\GdipCreateBitmapFromHBITMAP&quot;, &quot;Ptr&quot;, hbm, &quot;Ptr&quot;, 0, &quot;Ptr*&quot;, pBitmap)
	DllCall(&quot;SelectObject&quot;, &quot;Ptr&quot;, hdc, &quot;Ptr&quot;, obm)
	DllCall(&quot;DeleteObject&quot;, &quot;Ptr&quot;, hbm)
	DllCall(&quot;DeleteDC&quot;, &quot;Ptr&quot;, hdc)
	return pBitmap
}

ShowPreview(pBitmap, Time, X, Y, W, H) {
	static WS_EX_LAYERED := 0x80000
	SelectionHwnd := App.Selection.Hwnd

	while App.Windows[i := A_Index+9]
		continue

	Gui, %i%: -Caption +E%WS_EX_LAYERED% +LastFound +AlwaysOnTop +ToolWindow +Owner
	WinGet, PreviewHwnd
	Gui, %i%: Show, NA, ScreenShot

	hbm := CreateDIBSection(W, H)
	hdc := DllCall(&quot;CreateCompatibleDC&quot;, &quot;Ptr&quot;, 0)
	obm := DllCall(&quot;SelectObject&quot;, &quot;Ptr&quot;, hdc, &quot;Ptr&quot;, hbm)
	DllCall(&quot;gdiplus\GdipCreateFromHDC&quot;, &quot;Ptr&quot;, hdc, &quot;Ptr*&quot;, G)
	DrawImage(G, pBitmap, 0, 0, W, H, 0, 0, W, H)

	UpdateLayeredWindow(PreviewHwnd, hdc, X, Y, W, H)
	WinActivate, ahk_id %PreviewHwnd%

	App.Windows[i] := Object(&quot;Hwnd&quot;, PreviewHwnd, &quot;Time&quot;, Time, &quot;pBM&quot;, pBitmap)

	DllCall(&quot;SelectObject&quot;, &quot;Ptr&quot;, hdc, &quot;Ptr&quot;, obm)
	DllCall(&quot;DeleteObject&quot;, &quot;Ptr&quot;, hbm)
	DllCall(&quot;DeleteDC&quot;, &quot;Ptr&quot;, hdc), DllCall(&quot;gdiplus\GdipDeleteGraphics&quot;, &quot;Ptr&quot;, G)

	SetWinDelay, 10
	Loop 10 {
		if (App.Menu.CancelPreviewIntroHwnd = PreviewHwnd)
			break
		WinMove, ahk_id %PreviewHwnd%,, X+A_Index, Y-A_Index
	}

	DetectHiddenWindows, Off
	if !WinExist(&quot;ahk_id &quot; SelectionHwnd)
		ShowPreviewHighlight(PreviewHwnd)
	if (App.Menu.CancelPreviewIntroHwnd = PreviewHwnd)
		App.Menu.CancelPreviewIntroHwnd := &quot;&quot;
}

ShowPreviewHighlight(PreviewHwnd) {
	Critical
	if (App.Menu.CancelPreviewIntroHwnd = PreviewHwnd) {
		Critical, Off
		return
	}
	Highlight(PreviewHwnd)
	Critical, Off
	Sleep, 300
	HidePreviewHighlight()
}

Highlight(TargetHwnd) {
	HidePreviewHighlight()
	WinActivate, ahk_id %TargetHwnd%
	; WS_EX_LAYERED := 0x80000, WS_EX_TRANSPARENT := 0x20
	Gui, 5:-Caption +E0x80020 +LastFound +AlwaysOnTop +ToolWindow +Owner
	WinGet, HighlightHwnd
	App.Menu.HighlightHwnd := HighlightHwnd
	Gui, 5:Show, NA
	WinGetPos, X, Y, W, H, ahk_id %TargetHwnd%
	hbm := CreateDIBSection(W, H)
	hdc := DllCall(&quot;CreateCompatibleDC&quot;, &quot;Ptr&quot;, 0)
	obm := DllCall(&quot;SelectObject&quot;, &quot;Ptr&quot;, hdc, &quot;Ptr&quot;, hbm)
	DllCall(&quot;gdiplus\GdipCreateFromHDC&quot;, &quot;Ptr&quot;, hdc, &quot;Ptr*&quot;, G)

	DllCall(&quot;gdiplus\GdipCreateSolidFill&quot;, &quot;UInt&quot;, 0xffffaa00, &quot;Ptr*&quot;, pBrush)
	TH := 5	; толщина линии подсветки
	Points := &quot;0,0|&quot; W &quot;,0|&quot; W &quot;,&quot; H &quot;|0,&quot; H &quot;|0,&quot; TH &quot;|&quot; TH &quot;,&quot; 
		. TH &quot;|&quot; TH &quot;,&quot; H - TH &quot;|&quot; W - TH &quot;,&quot; H - TH &quot;|&quot; W - TH &quot;,&quot; TH &quot;|0,&quot; TH
	FillPolygon(G, pBrush, Points)
	DllCall(&quot;gdiplus\GdipDeleteBrush&quot;, &quot;Ptr&quot;, pBrush)

	UpdateLayeredWindow(HighlightHwnd, hdc, X, Y, W, H)

	DllCall(&quot;SelectObject&quot;, &quot;Ptr&quot;, hdc, &quot;Ptr&quot;, obm)
	DllCall(&quot;DeleteObject&quot;, &quot;Ptr&quot;, hbm), DllCall(&quot;DeleteDC&quot;, &quot;Ptr&quot;, hdc)
	DllCall(&quot;gdiplus\GdipDeleteGraphics&quot;, &quot;Ptr&quot;, G)
}

HidePreviewHighlight() {
	HighlightHwnd := App.Menu.HighlightHwnd
	if (HighlightHwnd &amp;&amp; WinExist(&quot;ahk_id &quot; HighlightHwnd))
		Gui, 5:Destroy
	App.Menu.HighlightHwnd := &quot;&quot;
}

FindHighlight() {
	static CurrentScr

	if !CurrentScr
		CurrentScr := App.Windows.MaxIndex()
	else {
		while !App.Windows[CurrentScr := CurrentScr = 10 ? App.Windows.MaxIndex() : CurrentScr - 1]
			continue
	}
	Highlight(App.Windows[CurrentScr].Hwnd)
	KeyWait, Space
	HidePreviewHighlight()
}

SaveScreenshot(pBitmap, OutFileName, OutputOverride := &quot;&quot;) {
	Output := OutputOverride = &quot;&quot; ? App.Config.Output : OutputOverride
	OutDirectory := App.Config.OutDirectory
	Ext := App.Config.Ext
	JPEGQuality := App.Config.JPEGQuality
	ImgbbSent := false
	MissingImgbbClientID := false

	if (File := InStr(Output, &quot;File&quot;)) {
		Critical
		IfNotExist, % OutDirectory
		{
			FileCreateDir, % OutDirectory
			if ErrorLevel {
				File := &quot;&quot;
				MsgBox, 48, Невозможно создать папку
					, Невозможно создать папку вывода.`nОшибка %A_LastError%
			}
		}

		if (Result := SaveBitmap(pBitmap, OutDirectory &quot;\&quot; OutFileName &quot;.&quot; Ext, JPEGQuality)) {
			File := &quot;&quot;
			Errors := { 2: &quot;Could not get a list of encoders on system&quot;
						 , 3: &quot;Could not find matching encoder for specified file format&quot;
						 , 4: &quot;Could not save file to disk&quot; }
			MsgBox, 48, Ошибка записи в файл, % Errors[-Result]
		}
	}
	if Clip := InStr(Output, &quot;Clip&quot;)
		SetBitmapToClipboard(pBitmap)
	if InStr(Output, &quot;Imgbb&quot;) {
		ClientID := EnsureImgbbClientId()
		if (ClientID = &quot;&quot;)
			MissingImgbbClientID := true
		else {
			SendImageToImgbb(pBitmap, ClientID)
			ImgbbSent := true
		}
	}
	if (App.Config.Notify) {
		if File &amp;&amp; Clip &amp;&amp; ImgbbSent
			NotifyText := &quot;Скриншот сохранён в файл,`nпомещён в буфер обмена`nи отправлен на imgbb.com&quot;
		else {
			NotifyText := &quot;Скриншот &quot;
			if File
				NotifyText .= (NotifyText = &quot;Скриншот &quot; ? &quot;&quot; : &quot;`nи &quot;) . &quot;сохранён в файл&quot;
			if Clip
				NotifyText .= (NotifyText = &quot;Скриншот &quot; ? &quot;&quot; : &quot;`nи &quot;) . &quot;помещён в буфер обмена&quot;
			if ImgbbSent
				NotifyText .= (NotifyText = &quot;Скриншот &quot; ? &quot;&quot; : &quot;`nи &quot;) . &quot;отправлен на imgbb.com&quot;
			if MissingImgbbClientID {
				if (NotifyText = &quot;Скриншот &quot;)
					NotifyText .= &quot;не отправлен на Imgbb!`nВпишите ClientID в ScreenCatcher.ini&quot;
				else
					NotifyText .= &quot;`nно не отправлен на Imgbb&quot;
			}
		}
		if (NotifyText = &quot;Скриншот &quot;)
			NotifyText := MissingImgbbClientID
				? &quot;Скриншот не отправлен на Imgbb!`nВпишите ClientID в ScreenCatcher.ini&quot;
				: &quot;Скриншот не сохранён!`nУкажите в настройках во вкладке &quot;&quot;Вывод&quot;&quot;`nвариант сохранения скриншота!&quot;
		new ToolTip({ text: NotifyText, FontSize: 14, FontName: &quot;calibri&quot;, TrayTip: true
							, TimeOut: -3500, BackColor: 0x0055aa, TextColor: &quot;white&quot;
							, Margin: [10, 5, 10, 5] })
	}
	DllCall(&quot;gdiplus\GdipDisposeImage&quot;, &quot;Ptr&quot;, pBitmap)
}

SendImageToImgbb(pBitmap, apiKey) {
	Ext := App.Config.Ext
	JPEGQuality := App.Config.JPEGQuality
	buff := Ext
	if ( (size := SaveBitmap(pBitmap, buff, JPEGQuality, 1)) &lt; 0 ) {
		Errors := { 2: &quot;Could not get a list of encoders on system&quot;
				  , 3: &quot;Could not find matching encoder for specified file format&quot;
				  , 4: &quot;Не удалось сохранить битмап в буфер&quot; }
		MsgBox, 48, Ошибка записи в буфер, % Errors[-size]
		return
	}
    strBase64 := EncodeDecodeURI(CryptBinaryToStringBASE64(&amp;buff, size, 1))
    http := ComObjCreate(&quot;WinHttp.WinHttpRequest.5.1&quot;)
    http.open(&quot;POST&quot;, &quot;https://api.imgbb.com/1/upload&quot;, true)
    http.SetRequestHeader(&quot;Content-Type&quot;, &quot;application/x-www-form-urlencoded&quot;)
    body := &quot;key=&quot; . apiKey . &quot;&amp;image=&quot; . strBase64
    http.send(body)
    http.WaitForResponse()
    if (http.status != 200)
       throw &quot;failed to upload image. Status: &quot; . http.status
	responsetext := http.responsetext
    RegExMatch(responsetext, &quot;&quot;&quot;url&quot;&quot;:&quot;&quot;(.*?)&quot;&quot;&quot;, match)
    link := StrReplace(match1, &quot;\&quot;)
	Gui, 5: Default
	try Gui, Destroy
	Gui, Font, s9
	Gui, Add, Edit, % &quot;w250 h&quot; . (link ? 24 : 120), % link ? link : responsetext
	Gui, Font
	Gui, Add, Button, x+-80 y+7 w80 h24 gCopyResponse Default, Copy
	GuiControl, Focus, Button1
	Gui, Show,, Imgbb.com
	VarSetCapacity(strBase64, 0), VarSetCapacity(buff, 0)
}

EncodeDecodeURI(str, encode := true, component := true) {
    static document := &quot;&quot;, JS := &quot;&quot;
    if !document {
        document := ComObjCreate(&quot;htmlfile&quot;)
        document.write(&quot;&lt;meta http-equiv=&quot;&quot;X-UA-Compatible&quot;&quot; content=&quot;&quot;IE=9&quot;&quot;&gt;&quot;)
        JS := document.parentWindow
        (document.documentMode &lt; 9 &amp;&amp; JS.execScript())
    }
    return JS[(encode ? &quot;en&quot; : &quot;de&quot;) . &quot;codeURI&quot; . (component ? &quot;Component&quot; : &quot;&quot;)](str)
}

CopyResponse() {
	GuiControlGet, Clipboard, 5:, Edit1
	Gui, 5: Destroy
}

WM_SHOWWINDOW(wp, lp, msg, hwnd) {
	if IsOverlaySelectionHwnd(hwnd)
		return
	if (A_Gui &lt; 10 || wp || A_Gui = &quot;Imgbb&quot;)	; окно показывается — wp = true, скрывается — wp = false
		return

	Gui, %A_Gui%:Destroy
	DllCall(&quot;gdiplus\GdipDisposeImage&quot;, &quot;Ptr&quot;, App.Windows[A_Gui].pBM)
	App.Windows.Remove(A_Gui, &quot;&quot;)
}

WM_COMMAND(info, wp, lp) {
	static EN_SETFOCUS := 0x100, EN_KILLFOCUS := 0x200
	
	notify := wp &gt;&gt; 16
	if !(notify = EN_SETFOCUS || notify = EN_KILLFOCUS)
		return
	
	for k, v in info {
		if (v = lp) {
			( notify = EN_SETFOCUS &amp;&amp; DllCall(&quot;HideCaret&quot;, &quot;Ptr&quot;, v) )
			SetStaticColor(v, notify = EN_SETFOCUS ? 0xFECC53 : 0xFFFFFF, 0)
		}
	}
}

WM_SETCURSOR(info, wp) {
   static hCursor, OCR_NORMAL := 32512
   ( !hCursor &amp;&amp; hCursor := LoadCursor(OCR_NORMAL) )
	for k, v in info {
		if (v = wp)
			return DllCall(&quot;SetCursor&quot;, &quot;Ptr&quot;, hCursor)
	}
}

WM_OVERLAY_SETCURSOR(wParam, lParam, msg, hwnd) {
	static hCross := DllCall(&quot;LoadCursor&quot;, &quot;Ptr&quot;, 0, &quot;Ptr&quot;, 32515, &quot;Ptr&quot;)
	if !IsOverlaySelectionHwnd(hwnd)
		return
	DllCall(&quot;SetCursor&quot;, &quot;Ptr&quot;, hCross)
	return true
}

IsOverlaySelectionHwnd(hwnd) {
	return hwnd &amp;&amp; IsObject(App.Selection.Overlay)
		&amp;&amp; IsObject(App.Selection.Overlay.overlay)
		&amp;&amp; hwnd = App.Selection.Overlay.overlay.hwnd
}

LoadCursor(cursorId) {
   static IMAGE_CURSOR := 2, LR_DEFAULTSIZE := 0x40, LR_SHARED := 0x8000
   return DllCall(&quot;LoadImage&quot;, &quot;Ptr&quot;, 0
                             , &quot;UInt&quot;, cursorId
                             , &quot;UInt&quot;, IMAGE_CURSOR
                             , &quot;Int&quot;, 0, &quot;Int&quot;, 0
                             , &quot;UInt&quot;, LR_DEFAULTSIZE|LR_SHARED, &quot;Ptr&quot;)
}

WM_ESC() {
	Gui, %A_Gui%:Cancel
}

WM_WHEEL(wp, lp, msg, hwnd) {
	static PrevTime = 0
	Critical

	App.Windows[A_Gui].Scale := 1		; значит, размер скриншота изменён

	pBitmap := App.Windows[A_Gui].pBM
	GetImageDimensions(pBitmap, WidthBM, HeightBM)
	RatioBM := WidthBM/HeightBM
	SquareBM := WidthBM*HeightBM

	WinGetPos, X, Y, W, H, A
	DeltaW := Round(W/(!wp ? 10 : 11))
	CtrlState := GetKeyState(&quot;Ctrl&quot;, &quot;P&quot;)
	DeltaW := !wp ? (CtrlState ? 1 : DeltaW) : -(CtrlState ? 1 : DeltaW)
	ResultW := W + DeltaW
	ResultH := Round(ResultW/RatioBM)
	ResultX := X - (CtrlState ? 0 : Round(DeltaW/2))
	ResultY := Y - (CtrlState ? 0 : Round((ResultH - H)/2))
	SquareWin := ResultW*ResultH
	if (!wp &amp;&amp; SquareWin &gt; 13000000) || (wp &amp;&amp; SquareWin &lt; 1000 &amp;&amp; SquareWin &lt; SquareBM) {
		if (A_TickCount - PrevTime &gt; 200) {
			SoundBeep
			PrevTime := A_TickCount
		}
		return
	}

	hbm := CreateDIBSection(ResultW, ResultH)
	hdc := DllCall(&quot;CreateCompatibleDC&quot;, &quot;Ptr&quot;, 0)
	obm := DllCall(&quot;SelectObject&quot;, &quot;Ptr&quot;, hdc, &quot;Ptr&quot;, hbm)
	DllCall(&quot;gdiplus\GdipCreateFromHDC&quot;, &quot;Ptr&quot;, hdc, &quot;Ptr*&quot;, G)
	DllCall(&quot;gdiplus\GdipSetInterpolationMode&quot;, &quot;Ptr&quot;, G, &quot;Int&quot;, App.Config.InterpolationMode-1)
	DrawImage(G, pBitmap, 0, 0, ResultW, ResultH, 0, 0, WidthBM, HeightBM)
	UpdateLayeredWindow(hwnd, hdc, ResultX, ResultY, ResultW, ResultH)

	DllCall(&quot;SelectObject&quot;, &quot;Ptr&quot;, hdc, &quot;Ptr&quot;, obm)
	DllCall(&quot;DeleteObject&quot;, &quot;Ptr&quot;, hbm), DllCall(&quot;DeleteDC&quot;, &quot;Ptr&quot;, hdc), DllCall(&quot;gdiplus\GdipDeleteGraphics&quot;, &quot;Ptr&quot;, G)
}

GetImageDimensions(pBitmap, ByRef Width, ByRef Height) {
	DllCall(&quot;gdiplus\GdipGetImageWidth&quot;, &quot;Ptr&quot;, pBitmap, &quot;UInt*&quot;, Width)
	DllCall(&quot;gdiplus\GdipGetImageHeight&quot;, &quot;Ptr&quot;, pBitmap, &quot;UInt*&quot;, Height)
}

GetVisibleWindowRect(hwnd, ByRef X, ByRef Y, ByRef W, ByRef H) {
	static DWMWA_EXTENDED_FRAME_BOUNDS := 9

	VarSetCapacity(rect, 16, 0)
	if DllCall(&quot;dwmapi\DwmGetWindowAttribute&quot;, &quot;Ptr&quot;, hwnd, &quot;UInt&quot;, DWMWA_EXTENDED_FRAME_BOUNDS
													, &quot;Ptr&quot;, &amp;rect, &quot;UInt&quot;, 16)
		return false

	left   := NumGet(rect,  0, &quot;Int&quot;)
	top    := NumGet(rect,  4, &quot;Int&quot;)
	right  := NumGet(rect,  8, &quot;Int&quot;)
	bottom := NumGet(rect, 12, &quot;Int&quot;)
	if (right &lt;= left || bottom &lt;= top)
		return false

	X := left, Y := top, W := right - left, H := bottom - top
	return true
}

FillPolygon(pGraphics, pBrush, Points, FillMode=0) {
	StringSplit, Points, Points, |
	VarSetCapacity(PointF, 8*Points0)   
	Loop, %Points0%
	{
		StringSplit, Coord, Points%A_Index%, `,
		NumPut(Coord1, PointF, 8*(A_Index-1), &quot;float&quot;), NumPut(Coord2, PointF, (8*(A_Index-1))+4, &quot;float&quot;)
	}   
	return DllCall(&quot;gdiplus\GdipFillPolygon&quot;, &quot;Ptr&quot;, pGraphics, &quot;Ptr&quot;, pBrush, &quot;Ptr&quot;, &amp;PointF, &quot;Int&quot;, Points0, &quot;Int&quot;, FillMode)
}

DrawImage(pGraphics, pBitmap, dx=&quot;&quot;, dy=&quot;&quot;, dw=&quot;&quot;, dh=&quot;&quot;, sx=&quot;&quot;, sy=&quot;&quot;, sw=&quot;&quot;, sh=&quot;&quot;) {
	if (sx = &quot;&quot; &amp;&amp; sy = &quot;&quot; &amp;&amp; sw = &quot;&quot; &amp;&amp; sh = &quot;&quot;) {
		if (dx = &quot;&quot; &amp;&amp; dy = &quot;&quot; &amp;&amp; dw = &quot;&quot; &amp;&amp; dh = &quot;&quot;) {
			sx := dx := 0, sy := dy := 0
			GetImageDimensions(pBitmap, sw, sh)
			dw := sw, dh := sh
		} else {
			sx := sy := 0
			GetImageDimensions(pBitmap, sw, sh)
		}
	}

	E := DllCall(&quot;gdiplus\GdipDrawImageRectRect&quot;, &quot;Ptr&quot;, pGraphics, &quot;Ptr&quot;, pBitmap, &quot;Float&quot;, dx, &quot;Float&quot;, dy, &quot;Float&quot;, dw, &quot;Float&quot;, dh
															  , &quot;Float&quot;, sx, &quot;Float&quot;, sy, &quot;Float&quot;, sw, &quot;Float&quot;, sh, &quot;int&quot;, 2, &quot;Ptr&quot;, 0, &quot;Ptr&quot;, 0, &quot;Ptr&quot;, 0)
	return E
}

BitmapFromScreen(Screen, Cursor, Raster=&quot;&quot;) {
	if (Screen = 0) {
		Sysget, x, 76
		Sysget, y, 77
		Sysget, w, 78
		Sysget, h, 79
	} else {
		StringSplit, S, Screen, |
		x := S1, y := S2, w := S3, h := S4
	}
	chdc := DllCall(&quot;CreateCompatibleDC&quot;, &quot;Ptr&quot;, 0, &quot;Ptr&quot;)
	VarSetCapacity(bi, 40, 0)
	NumPut(w, bi, 4, &quot;UInt&quot;), NumPut(h, bi, 8, &quot;UInt&quot;), NumPut(40, bi, 0, &quot;UInt&quot;), NumPut(1, bi, 12, &quot;UShort&quot;)
	NumPut(0, bi, 16), NumPut(32, bi, 14, &quot;UShort&quot;)
	hbm := DllCall(&quot;CreateDIBSection&quot;, &quot;Ptr&quot; , chdc, &quot;Ptr&quot; , &amp;bi, &quot;UInt&quot; , 0, &quot;Ptr*&quot;, 0, &quot;Ptr&quot; , 0, &quot;UInt&quot; , 0, &quot;Ptr&quot;)
	obm := DllCall(&quot;SelectObject&quot;, &quot;Ptr&quot;, chdc, &quot;Ptr&quot;, hbm, &quot;Ptr&quot;)
	hhdc := DllCall(&quot;GetDC&quot;, &quot;Ptr&quot;, 0, &quot;Ptr&quot;)
	DllCall(&quot;BitBlt&quot;, &quot;Ptr&quot;, chdc, &quot;Int&quot;, 0, &quot;Int&quot;, 0, &quot;Int&quot;, w, &quot;Int&quot;, h, &quot;Ptr&quot;, hhdc, &quot;Int&quot;, x, &quot;Int&quot;, y, &quot;UInt&quot;, Raster)
	DllCall(&quot;ReleaseDC&quot;, &quot;Ptr&quot;, 0, &quot;Ptr&quot;, hhdc)
	( Cursor &amp;&amp; CaptureCursor(chdc, x, y) )
	DllCall(&quot;gdiplus\GdipCreateBitmapFromHBITMAP&quot;, &quot;Ptr&quot;, hbm, &quot;Ptr&quot;, 0, &quot;Ptr*&quot;, pBitmap)
	DllCall(&quot;SelectObject&quot;, &quot;Ptr&quot;, chdc, &quot;Ptr&quot;, obm, &quot;Ptr&quot;)
	DllCall(&quot;DeleteObject&quot;, &quot;Ptr&quot;, hbm)
	DllCall(&quot;DeleteDC&quot;, &quot;Ptr&quot;, hhdc)
	DllCall(&quot;DeleteDC&quot;, &quot;Ptr&quot;, chdc)
	return pBitmap
}

CaptureCursor(hDC, x, y) {
	VarSetCapacity(CURSORINFO, szCI := 4*2 + A_PtrSize + 8, 0)
	NumPut(szCI, CURSORINFO)
	DllCall(&quot;GetCursorInfo&quot;, &quot;Ptr&quot;, &amp;CURSORINFO)
	bShow   := NumGet(CURSORINFO, 4, &quot;UInt&quot;)
	hCursor := NumGet(CURSORINFO, 4*2)
	xCursor := NumGet(CURSORINFO, 4*2 + A_PtrSize, &quot;Int&quot;)
	yCursor := NumGet(CURSORINFO, 4*2 + A_PtrSize + 4, &quot;Int&quot;)

	if bShow &amp;&amp; hCursor := DllCall(&quot;CopyIcon&quot;, &quot;Ptr&quot;, hCursor, &quot;Ptr&quot;) {
		VarSetCapacity(ICONINFO, 4*2 + A_PtrSize*3, 0)
		DllCall(&quot;GetIconInfo&quot;, &quot;Ptr&quot;, hCursor, &quot;Ptr&quot;, &amp;ICONINFO)
		bIcon    := NumGet(ICONINFO, &quot;UInt&quot;)
		xHotspot := NumGet(ICONINFO, 4, &quot;UInt&quot;)
		yHotspot := NumGet(ICONINFO, 4*2, &quot;UInt&quot;)
		hBMMask  := NumGet(ICONINFO, 4*2 + A_PtrSize)
		hBMColor := NumGet(ICONINFO, 4*2 + A_PtrSize*2)

		DllCall(&quot;DrawIcon&quot;, &quot;Ptr&quot;, hDC, &quot;Int&quot;, xCursor - xHotspot - x, &quot;Int&quot;, yCursor - yHotspot - y, &quot;Ptr&quot;, hCursor)
		DllCall(&quot;DestroyIcon&quot;, &quot;Ptr&quot;, hCursor)
		( hBMMask &amp;&amp; DllCall(&quot;DeleteObject&quot;, &quot;Ptr&quot;, hBMMask) )
		( hBMColor &amp;&amp; DllCall(&quot;DeleteObject&quot;, &quot;Ptr&quot;, hBMColor) )
	}
}

ScreenShotMenuHandling() {
	CurrentGui := App.Menu.CurrentPreviewGui
	pBitmap := App.Windows[CurrentGui].pBM
	Time := App.Windows[CurrentGui].Time

	if (A_ThisMenuItem = &quot;Сохранить&quot; || A_ThisMenuItem = &quot;Сохранить в первоначальном размере&quot;)
		SaveScreenshot(pBitmap, Time)
	else if (A_ThisMenuItem = &quot;Сохранить в текущем размере&quot;)
		SavePreviewCurrentSize(CurrentGui, pBitmap, Time)
	Gui, %CurrentGui%:Cancel
}

PreviewSaveToMenuHandling() {
	CurrentGui := App.Menu.CurrentPreviewGui
	pBitmap := App.Windows[CurrentGui].pBM
	Time := App.Windows[CurrentGui].Time
	Output := PreviewSaveToOutput(A_ThisMenuItem)

	if (Output != &quot;&quot;)
		SaveScreenshot(pBitmap, Time, Output)
	Gui, %CurrentGui%:Cancel
}

PreviewSaveCurrentToMenuHandling() {
	CurrentGui := App.Menu.CurrentPreviewGui
	pBitmap := App.Windows[CurrentGui].pBM
	Time := App.Windows[CurrentGui].Time
	Output := PreviewSaveToOutput(A_ThisMenuItem)

	if (Output != &quot;&quot;)
		SavePreviewCurrentSize(CurrentGui, pBitmap, Time, Output)
	Gui, %CurrentGui%:Cancel
}

SavePreviewCurrentSize(CurrentGui, pBitmap, Time, OutputOverride := &quot;&quot;) {
	WinGetPos,,, W, H, % &quot;ahk_id &quot; App.Windows[CurrentGui].Hwnd
	DllCall(&quot;gdiplus\GdipCreateBitmapFromScan0&quot;, &quot;Int&quot;, W, &quot;Int&quot;, H, &quot;Int&quot;, 0, &quot;Int&quot;, 0x26200A, &quot;Ptr&quot;, 0, &quot;Ptr*&quot;, pBM)
	DllCall(&quot;gdiplus\GdipGetImageGraphicsContext&quot;, &quot;Ptr&quot;, pBM, &quot;Ptr*&quot;, G)
	DllCall(&quot;gdiplus\GdipSetInterpolationMode&quot;, &quot;Ptr&quot;, G, &quot;Int&quot;, App.Config.InterpolationMode-1)

	GetImageDimensions(pBitmap, WidthBM, HeightBM)
	DrawImage(G, pBitmap, -Round(W/400), -Round(H/400)
		, Ceil(W*1.005), Ceil(H*1.005), 0, 0, WidthBM, HeightBM)

	DllCall(&quot;gdiplus\GdipDeleteGraphics&quot;, &quot;Ptr&quot;, G)
	SaveScreenshot(pBM, Time, OutputOverride)
}

PreviewSaveToOutput(MenuItem) {
	if (MenuItem = &quot;файл&quot;)
		return &quot;File&quot;
	if (MenuItem = &quot;буфер обмена&quot;)
		return &quot;Clip&quot;
	if (MenuItem = &quot;imgbb.com&quot;)
		return &quot;Imgbb&quot;
	return &quot;&quot;
}

SaveBitmap(pBitmap, ByRef info, Quality := 75, tobuff := &quot;&quot;) {
	if tobuff
		Extension := info
	else
		SplitPath, info,,, Extension
   if Extension not in BMP,DIB,RLE,JPG,JPEG,JPE,JFIF,GIF,TIF,TIFF,PNG
      return -1

   DllCall(&quot;gdiplus\GdipGetImageEncodersSize&quot;, &quot;UInt*&quot;, nCount, &quot;UInt*&quot;, nSize)
   VarSetCapacity(ci, nSize)
   DllCall(&quot;gdiplus\GdipGetImageEncoders&quot;, &quot;UInt&quot;, nCount, &quot;UInt&quot;, nSize, &quot;Ptr&quot;, &amp;ci)
   if !(nCount &amp;&amp; nSize)
      return -2
   
	Loop, % nCount {
		sString := StrGet(NumGet(ci, (idx := (48+7*A_PtrSize)*(A_Index-1))+32+3*A_PtrSize), &quot;UTF-16&quot;)
		if !InStr(sString, &quot;*.&quot; Extension)
			continue
		
		pCodec := &amp;ci+idx
		break
	}
   
   if !pCodec
      return -3
	
	if RegExMatch(Extension, &quot;i)^J(PG|PEG|PE|FIF)$&quot;) &amp;&amp; Quality != 75 {
		DllCall(&quot;gdiplus\GdipGetEncoderParameterListSize&quot;, &quot;Ptr&quot;, pBitmap, &quot;Ptr&quot;, pCodec, &quot;UInt*&quot;, nSize)
		VarSetCapacity(EncoderParameters, nSize, 0)
		DllCall(&quot;gdiplus\GdipGetEncoderParameterList&quot;, &quot;Ptr&quot;, pBitmap, &quot;Ptr&quot;, pCodec, &quot;UInt&quot;, nSize, &quot;Ptr&quot;, &amp;EncoderParameters)
		Loop, % NumGet(EncoderParameters, &quot;UInt&quot;) {
			elem := (24+A_PtrSize)*(A_Index-1) + 4 + (pad := A_PtrSize = 8 ? 4 : 0)
			if (NumGet(EncoderParameters, elem+16, &quot;UInt&quot;) = 1) &amp;&amp; (NumGet(EncoderParameters, elem+20, &quot;UInt&quot;) = 6) {
				p := elem+&amp;EncoderParameters-pad-4
				NumPut(Quality, NumGet(NumPut(4, NumPut(1, p+0)+20, &quot;UInt&quot;)), &quot;UInt&quot;)
				break
			}
		}      
	}
	if !tobuff {
		if A_IsUnicode
			pOutput := &amp;info
		else {
			VarSetCapacity(wOutput, StrPut(info, &quot;UTF-16&quot;)*2, 0)
			StrPut(info, &amp;wOutput, &quot;UTF-16&quot;)
			pOutput := &amp;wOutput
		}
		E := DllCall(&quot;gdiplus\GdipSaveImageToFile&quot;, &quot;Ptr&quot;, pBitmap, &quot;Ptr&quot;, pOutput, &quot;Ptr&quot;, pCodec, &quot;UInt&quot;, p ? p : 0)
	}
	else {
		DllCall( &quot;ole32\CreateStreamOnHGlobal&quot;, &quot;UInt&quot;, 0, &quot;Int&quot;, 1, &quot;Ptr*&quot;, pStream )
		if E := DllCall( &quot;gdiplus\GdipSaveImageToStream&quot;, &quot;Ptr&quot;, pBitmap, &quot;Ptr&quot;, pStream, &quot;Ptr&quot;, pCodec, &quot;UInt&quot;, p ? p : 0 )
			DllCall( NumGet( NumGet( 1*pStream ) + A_PtrSize*2 ), &quot;Ptr&quot;, pStream )
		else {
			DllCall( &quot;ole32\GetHGlobalFromStream&quot;, &quot;Ptr&quot;, pStream, &quot;Ptr*&quot;, hData )
			pData := DllCall( &quot;GlobalLock&quot;, &quot;Ptr&quot;, hData )
			nSize := DllCall( &quot;GlobalSize&quot;, &quot;Ptr&quot;, hData )
			VarSetCapacity( info, 0), VarSetCapacity( info, nSize, 0 )
			DllCall( &quot;RtlMoveMemory&quot;, &quot;Ptr&quot;, &amp;info, &quot;Ptr&quot;, pData, &quot;UInt&quot;, nSize )
			DllCall( &quot;GlobalUnlock&quot;, &quot;Ptr&quot;, hData )
			DllCall( NumGet( NumGet( 1*pStream ) + A_PtrSize*2 ), &quot;Ptr&quot;, pStream )
			DllCall( &quot;GlobalFree&quot;, &quot;Ptr&quot;, hData )
		}
	}
   return E ? -4 : tobuff ? nSize : 0
}

SetBitmapToClipboard(pBitmap) {
	off1 := A_PtrSize = 8 ? 52 : 44, off2 := A_PtrSize = 8 ? 32 : 24
	DllCall(&quot;gdiplus\GdipCreateHBITMAPFromBitmap&quot;, &quot;Ptr&quot;, pBitmap, &quot;Ptr*&quot;, hBitmap, &quot;Int&quot;, 0xFFFFFFFF)
	DllCall(&quot;GetObject&quot;, &quot;Ptr&quot;, hBitmap, &quot;int&quot;, VarSetCapacity(oi, A_PtrSize = 8 ? 104 : 84, 0), &quot;Ptr&quot;, &amp;oi)
	hdib := DllCall(&quot;GlobalAlloc&quot;, &quot;UInt&quot;, 2, &quot;Ptr&quot;, 40+NumGet(oi, off1, &quot;UInt&quot;), &quot;Ptr&quot;)
	pdib := DllCall(&quot;GlobalLock&quot;, &quot;Ptr&quot;, hdib, &quot;Ptr&quot;)
	DllCall(&quot;RtlMoveMemory&quot;, &quot;Ptr&quot;, pdib, &quot;Ptr&quot;, &amp;oi+off2, &quot;Ptr&quot;, 40)
	DllCall(&quot;RtlMoveMemory&quot;, &quot;Ptr&quot;, pdib+40, &quot;Ptr&quot;, NumGet(oi, off2 - A_PtrSize, &quot;Ptr&quot;), &quot;Ptr&quot;, NumGet(oi, off1, &quot;UInt&quot;))
	DllCall(&quot;GlobalUnlock&quot;, &quot;Ptr&quot;, hdib)
	DllCall(&quot;DeleteObject&quot;, &quot;Ptr&quot;, hBitmap)
	DllCall(&quot;OpenClipboard&quot;, &quot;Ptr&quot;, 0)
	DllCall(&quot;EmptyClipboard&quot;)
	DllCall(&quot;SetClipboardData&quot;, &quot;UInt&quot;, 8, &quot;Ptr&quot;, hdib)
	DllCall(&quot;CloseClipboard&quot;)
}

CreateDIBSection(w, h, hdc=&quot;&quot;, bpp=32, ByRef ppvBits=0) {
	hdc2 := hdc ? hdc : DllCall(&quot;GetDC&quot;, &quot;Ptr&quot;, 0)
	VarSetCapacity(bi, 40, 0)
	
	NumPut(w, bi, 4, &quot;uint&quot;)
	NumPut(h, bi, 8, &quot;uint&quot;)
	NumPut(40, bi, 0, &quot;uint&quot;)
	NumPut(1, bi, 12, &quot;ushort&quot;)
	NumPut(0, bi, 16, &quot;uInt&quot;)
	NumPut(bpp, bi, 14, &quot;ushort&quot;)
	
	hbm := DllCall(&quot;CreateDIBSection&quot;, &quot;Ptr&quot;, hdc2, &quot;Ptr&quot;, &amp;bi, &quot;UInt&quot;, 0, &quot;Ptr*&quot;, ppvBits, &quot;Ptr&quot;, 0, &quot;UInt&quot;, 0, &quot;Ptr&quot;)

	( !hdc &amp;&amp; DllCall(&quot;ReleaseDC&quot;, &quot;Ptr&quot;, 0, &quot;Ptr&quot;, hdc2) )
	return hbm
}

UpdateLayeredWindow(hwnd, hdc, x=&quot;&quot;, y=&quot;&quot;, w=&quot;&quot;, h=&quot;&quot;, Alpha=255) {
	if ((x != &quot;&quot;) &amp;&amp; (y != &quot;&quot;))
		VarSetCapacity(pt, 8), NumPut(x, pt, 0, &quot;UInt&quot;), NumPut(y, pt, 4, &quot;UInt&quot;)

	if (w = &quot;&quot;) ||(h = &quot;&quot;)
		WinGetPos,,, w, h, ahk_id %hwnd%
   
	return DllCall( &quot;UpdateLayeredWindow&quot;, &quot;Ptr&quot;, hwnd, &quot;Ptr&quot;, 0, &quot;Ptr&quot;, ((x = &quot;&quot;) &amp;&amp; (y = &quot;&quot;)) ? 0 : &amp;pt, &quot;Int64*&quot;, w|h&lt;&lt;32
													, &quot;Ptr&quot;, hdc, &quot;Int64*&quot;, 0, &quot;UInt&quot;, 0, &quot;UInt*&quot;, Alpha&lt;&lt;16|1&lt;&lt;24, &quot;UInt&quot;, 2 )
}

GdipStartup() {
	if !DllCall(&quot;GetModuleHandle&quot;, &quot;Str&quot;, &quot;gdiplus&quot;, &quot;Ptr&quot;)
		DllCall(&quot;LoadLibrary&quot;, &quot;Str&quot;, &quot;gdiplus&quot;)
	VarSetCapacity(si, A_PtrSize = 8 ? 24 : 16, 0), si := Chr(1)
	DllCall(&quot;gdiplus\GdiplusStartup&quot;, &quot;Ptr*&quot;, pToken, &quot;Ptr&quot;, &amp;si, &quot;Ptr&quot;, 0)
	return pToken
}

GdipShutdown(pToken) {
	DllCall(&quot;gdiplus\GdiplusShutdown&quot;, &quot;Ptr&quot;, pToken)
	if hModule := DllCall(&quot;GetModuleHandle&quot;, &quot;Str&quot;, &quot;gdiplus&quot;, &quot;Ptr&quot;)
		DllCall(&quot;FreeLibrary&quot;, &quot;Ptr&quot;, hModule)
	return 0
}

SetStaticColor(hStatic, b_color, f_color := 0) {
	static arr := [], GWL_WNDPROC := -4
	b_color := DllCall(&quot;Ws2_32\ntohl&quot;, &quot;UInt&quot;, b_color &lt;&lt; 8, &quot;UInt&quot;)
	f_color := DllCall(&quot;Ws2_32\ntohl&quot;, &quot;UInt&quot;, f_color &lt;&lt; 8, &quot;UInt&quot;)
	hGui := DllCall(&quot;GetParent&quot;, &quot;Ptr&quot;, hStatic, &quot;Ptr&quot;)
	if !arr.HasKey(hGui) {
		arr[hGui] := {}, arr[hGui].Statics := []
		arr[hGui].ProcOld := DllCall(&quot;SetWindowLong&quot; . (A_PtrSize = 8 ? &quot;Ptr&quot; : &quot;&quot;), &quot;Ptr&quot;, hGui, &quot;Int&quot;, GWL_WNDPROC
														, &quot;Ptr&quot;, RegisterCallback(&quot;WindowProc&quot;, &quot;&quot;, 4, Object(arr[hGui])), &quot;Ptr&quot;)
	}
	else if arr[hGui].Statics.HasKey(hStatic)
		DllCall(&quot;DeleteObject&quot;, &quot;Ptr&quot;, arr[hGui].Statics[hStatic].hBrush)
	arr[hGui].Statics[hStatic] := { b_color: b_color, f_color: f_color
								  , hBrush: DllCall(&quot;CreateSolidBrush&quot;, &quot;UInt&quot;, b_color, &quot;Ptr&quot;) }
	WinSet, Redraw,, ahk_id %hStatic%
}

WindowProc(hwnd, uMsg, wParam, lParam) {
	Critical
	static WM_CTLCOLORSTATIC := 0x138
	obj := Object(A_EventInfo)
	if (uMsg = WM_CTLCOLORSTATIC &amp;&amp; k := obj.Statics[lParam]) {
		DllCall(&quot;SetBkColor&quot;, &quot;Ptr&quot;, wParam, &quot;UInt&quot;, k.b_color)
		DllCall(&quot;SetTextColor&quot;, &quot;Ptr&quot;, wParam, &quot;UInt&quot;, k.f_color)
		return k.hBrush
	}
	return DllCall(&quot;CallWindowProc&quot;, &quot;Ptr&quot;, obj.ProcOld, &quot;Ptr&quot;, hwnd, &quot;UInt&quot;, uMsg, &quot;Ptr&quot;, wParam, &quot;Ptr&quot;, lParam)
}

CryptBinaryToStringBASE64(pData, Bytes, NOCRLF = &quot;&quot;) {
    static CRYPT_STRING_BASE64 := 1, CRYPT_STRING_NOCRLF := 0x40000000
    CRYPT := CRYPT_STRING_BASE64 | (NOCRLF ? CRYPT_STRING_NOCRLF : 0)
    
    DllCall(&quot;Crypt32\CryptBinaryToString&quot;, &quot;Ptr&quot;, pData, &quot;UInt&quot;, Bytes, &quot;UInt&quot;, CRYPT, &quot;Ptr&quot;, 0, &quot;UInt*&quot;, Chars)
    VarSetCapacity(OutData, Chars * (A_IsUnicode ? 2 : 1))
    DllCall(&quot;Crypt32\CryptBinaryToString&quot;, &quot;Ptr&quot;, pData, &quot;UInt&quot;, Bytes, &quot;UInt&quot;, CRYPT, &quot;Str&quot;, OutData, &quot;UInt*&quot;, Chars)
    return OutData
}

CreateIconFromBase64(StringBASE64, Size) {
    DllCall(&quot;Crypt32.dll\CryptStringToBinary&quot;, &quot;Ptr&quot;, &amp;StringBase64
       , &quot;UInt&quot;, StrLen(StringBase64), &quot;UInt&quot;, CRYPT_STRING_BASE64 := 1, &quot;UInt&quot;, 0, &quot;UInt*&quot;, Bytes, &quot;UInt*&quot;, 0, &quot;UInt*&quot;, 0)
 
    VarSetCapacity(IconData, Bytes) 
    DllCall(&quot;Crypt32.dll\CryptStringToBinary&quot;, &quot;Ptr&quot;, &amp;StringBase64 
       , &quot;UInt&quot;, StrLen(StringBase64), &quot;UInt&quot;, CRYPT_STRING_BASE64, &quot;Str&quot;, IconData, &quot;UInt*&quot;, Bytes, &quot;UInt*&quot;, 0, &quot;UInt*&quot;, 0)
       
    return DllCall(&quot;CreateIconFromResourceEx&quot;, &quot;Ptr&quot;, &amp;IconData + 4
       , &quot;UInt&quot;, NumGet(&amp;IconData, &quot;UInt&quot;), &quot;UInt&quot;, true, &quot;UInt&quot;, 0x30000, &quot;Int&quot;, Size, &quot;Int&quot;, Size, &quot;UInt&quot;, 0, &quot;Ptr&quot;)
}

GetBase64String(Size) {
	Icon16 = 
	(RTrim Join
	aAQAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAD///+3
	////////////////////////////////////////////////////////////////
	//////////////+3/////8H2/v865Pz/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/zLi/P+u9P7///////////8w4vz/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/H+D8////////////
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P///////////xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z///////////8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8////////////
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P///////////xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z///////////8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8////////////
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P///////////xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z///////////8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8////////////
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P///////////zDi/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8f4Pz////////////G9/7/Sub9/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P9A5Pz/tPT+//////////+3
	////////////////////////////////////////////////////////////////
	//////////////+3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
	)
	
	Icon32 = 
	(RTrim Join
	qBAAACgAAAAgAAAAQAAAAAEAIAAAAAAAgBAAAAAAAAAAAAAAAAAAAAAAAAC9vr5avb6+5L2+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/
	vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7kvb6+Wr2+vvO9vr7/vb6+/72+vv+9vr7/
	vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/
	vb6+/72+vv+9vr7zvb6+/72+vv+LyNH/Ldv1/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/y3b9f+LyNH/vb6+/72+vv+9vr7/vb6+/ybd+P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/ybd+P+9vr7/vb6+/72+vv+zwML/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/7PAwv+9vr7/vb6+/7PAwv8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/s8DC/72+vv+9vr7/s8DC/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P+zwML/vb6+/72+vv+zwML/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/7PAwv+9vr7/vb6+/7PAwv8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/s8DC/72+vv+9vr7/s8DC/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P+zwML/vb6+/72+vv+zwML/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/7PAwv+9vr7/vb6+/7PAwv8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/s8DC/72+vv+9vr7/s8DC/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P+zwML/vb6+/72+vv+zwML/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/7PAwv+9vr7/vb6+/7PAwv8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/s8DC/72+vv+9vr7/s8DC/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P+zwML/vb6+/72+vv+zwML/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/7PAwv+9vr7/vb6+/7PAwv8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/s8DC/72+vv+9vr7/s8DC/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P+zwML/vb6+/72+vv+zwML/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/7PAwv+9vr7/vb6+/7PAwv8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/s8DC/72+vv+9vr7/s8DC/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P+zwML/vb6+/72+vv+zwML/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/7PAwv+9vr7/vb6+/7PAwv8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/s8DC/72+vv+9vr7/s8DC/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P+zwML/vb6+/72+vv+zwML/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/7PAwv+9vr7/vb6+/7PAwv8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/s8DC/72+vv+9vr7/s8DC/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P+zwML/vb6+/72+vv+9vr7/Jt34/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Jt34/72+vv+9vr7/vb6+/72+vv+NyND/O9jw/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/
	Gt/8/xrf/P8a3/z/Gt/8/xrf/P8a3/z/Gt/8/zvY8P+NyND/vb6+/72+vv+9vr72vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/
	vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+9r2+vmC9vr7wvb6+/72+vv+9vr7/
	vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/vb6+/72+vv+9vr7/
	vb6+/72+vvC9vr5gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
	)
	return Icon%Size%
}

; Full-screen mouse selection overlay.
class OverlaySelection {
    static WM_MOUSEMOVE := 0x0200

    __New(thickness := 2, color := 0x00FF00, labelSpecs := &quot;&quot;, armedStyle := &quot;&quot;) {
        this.scale := A_ScreenDPI / 96
        this.overlay := new SelectionOverlayFrame(thickness, color, this.scale)
        this.state := &quot;idle&quot;
        this.busy := false
        this.startX := 0
        this.startY := 0
        this.curX := 0
        this.curY := 0
        this.callback := &quot;&quot;
        this.callbackTimer := &quot;&quot;
        this.disposed := false
        this.onMove := ObjBindMethod(this, &quot;OnMouseMove&quot;)
        this.labels := []
        this.providers := []

        specs := IsObject(labelSpecs) ? labelSpecs : OverlaySelection.DefaultSpecs()
        for index, spec in specs {
            style := spec.HasKey(&quot;style&quot;) ? spec.style : {}
            this.labels.Push(new SelectionOverlayLabel(style, this.scale))
            this.providers.Push(spec.provider)
        }

        if !IsObject(armedStyle)
            armedStyle := {}
        this.armedLabel := new SelectionOverlayLabel(armedStyle, this.scale)
        this.armedOffsetX := armedStyle.HasKey(&quot;offsetX&quot;) ? armedStyle.offsetX : 12
        this.armedOffsetY := armedStyle.HasKey(&quot;offsetY&quot;) ? armedStyle.offsetY : 12
    }

    DefaultSpecs() {
        gap := 5
        return [ {style: {}, provider: Func(&quot;OverlayTopLabelProvider&quot;).Bind(gap)}
               , {style: {}, provider: Func(&quot;OverlayBottomLabelProvider&quot;).Bind(gap)} ]
    }

    Arm(callback, showArmedLabel := true) {
        if (this.state != &quot;idle&quot; || this.busy)
            return

        this.callback := callback
        this.overlay.ClearFrame()
        this.overlay.Show()
        this.state := &quot;armed&quot;
        OnMessage(this.WM_MOUSEMOVE, this.onMove)
        MouseGetPos, mx, my
        this.curX := mx
        this.curY := my
        if showArmedLabel
            this.UpdateArmed()
    }

    BeginDrag() {
        if (this.state != &quot;armed&quot;)
            return

        MouseGetPos, mx, my
        this.startX := mx
        this.startY := my
        this.curX := mx
        this.curY := my
        this.overlay.ClearFrame()
        this.state := &quot;dragging&quot;
        this.Update()
    }

    Quick(callback) {
        if (this.state != &quot;idle&quot; || this.busy)
            return

        this.Arm(callback, false)
        this.BeginDrag()
        if !GetKeyState(&quot;LButton&quot;, &quot;P&quot;)
            this.Finish()
    }

    Cancel() {
        if (this.state != &quot;armed&quot; &amp;&amp; this.state != &quot;dragging&quot;)
            return

        OnMessage(this.WM_MOUSEMOVE, this.onMove, 0)
        this.overlay.Hide()
        this.state := &quot;idle&quot;
    }

    OnMouseMove(wParam, lParam, msg, hwnd) {
        if (hwnd != this.overlay.hwnd)
            return

        pt := this.ClientPoint(lParam)
        this.curX := pt.x
        this.curY := pt.y
        if (this.state = &quot;dragging&quot;)
            this.Update()
        else if (this.state = &quot;armed&quot;)
            this.UpdateArmed()
    }

    ClientPoint(lParam) {
        x := lParam &amp; 0xFFFF
        y := (lParam &gt;&gt; 16) &amp; 0xFFFF
        if (x &gt; 0x7FFF)
            x -= 0x10000
        if (y &gt; 0x7FFF)
            y -= 0x10000

        VarSetCapacity(pt, 8, 0)
        NumPut(x, pt, 0, &quot;Int&quot;)
        NumPut(y, pt, 4, &quot;Int&quot;)
        DllCall(&quot;ClientToScreen&quot;, &quot;Ptr&quot;, this.overlay.hwnd, &quot;Ptr&quot;, &amp;pt)
        return {x: NumGet(pt, 0, &quot;Int&quot;), y: NumGet(pt, 4, &quot;Int&quot;)}
    }

    Rect() {
        x := Min(this.startX, this.curX)
        y := Min(this.startY, this.curY)
        w := Abs(this.curX - this.startX)
        h := Abs(this.curY - this.startY)
        return {x: x, y: y, w: w, h: h}
    }

    Update() {
        r := this.Rect()
        ctx := {r: r, t: this.overlay.PhysT(), scale: this.scale}
        for index, provider in this.providers {
            spec := provider.Call(ctx)
            this.labels[index].SetText(spec.text, spec.ax, spec.ay, spec.side)
        }
        this.overlay.Render(r, this.labels)
    }

    UpdateArmed() {
        x := this.curX
        y := this.curY
        anchorX := x - Round(this.armedOffsetX * this.scale)
        anchorY := y - Round(this.armedOffsetY * this.scale)
        this.armedLabel.SetText(x . &quot;, &quot; . y, anchorX, anchorY, &quot;topLeft&quot;)
        this.overlay.RenderLabels([this.armedLabel])
    }

    Finish() {
        if (this.state != &quot;dragging&quot;)
            return

        this.state := &quot;idle&quot;
        OnMessage(this.WM_MOUSEMOVE, this.onMove, 0)
        this.overlay.Hide()
        callback := this.callback
        if IsObject(callback) {
            this.busy := true
            r := this.Rect()
            this.callbackTimer := ObjBindMethod(this, &quot;InvokeCallback&quot;, callback, r)
            timer := this.callbackTimer
            SetTimer, % timer, -1
        }
    }

    InvokeCallback(callback, r) {
        try callback.Call(r)
        finally {
            this.busy := false
            this.callbackTimer := &quot;&quot;
        }
    }

    Dispose() {
        if (this.disposed)
            return
        this.disposed := true

        if (this.state = &quot;armed&quot; || this.state = &quot;dragging&quot;)
            OnMessage(this.WM_MOUSEMOVE, this.onMove, 0)
        if IsObject(this.callbackTimer) {
            timer := this.callbackTimer
            SetTimer, % timer, Delete
            this.callbackTimer := &quot;&quot;
        }
        for index, label in this.labels
            label.Dispose()
        if IsObject(this.armedLabel)
            this.armedLabel.Dispose()
        if IsObject(this.overlay)
            this.overlay.Dispose()
        this.state := &quot;idle&quot;
    }

    __Delete() {
        this.Dispose()
    }
}

class SelectionOverlayFrame {
    __New(thickness, color, scale := 1) {
        this.t := thickness
        this.scale := scale
        this.color := 0xFF000000 | color
        this.w := A_ScreenWidth
        this.h := A_ScreenHeight
        this.prev := &quot;&quot;
        this.disposed := false
		this.guiName := &quot;SelectionOverlay&quot;

		Gui, % this.guiName . &quot;:New&quot;, +AlwaysOnTop -Caption +ToolWindow -DPIScale +E0x80000 +hwndOverlayHwnd
        this.hwnd := OverlayHwnd
		this.hArrow := DllCall(&quot;LoadCursor&quot;, &quot;Ptr&quot;, 0, &quot;Ptr&quot;, 32512, &quot;Ptr&quot;)
        this.CreateBuffer()
    }

    CreateBuffer() {
        this.hdcMem := DllCall(&quot;CreateCompatibleDC&quot;, &quot;Ptr&quot;, 0, &quot;Ptr&quot;)

        VarSetCapacity(bi, 40, 0)
        NumPut(40, bi, 0, &quot;UInt&quot;)
        NumPut(this.w, bi, 4, &quot;Int&quot;)
        NumPut(-this.h, bi, 8, &quot;Int&quot;)
        NumPut(1, bi, 12, &quot;UShort&quot;)
        NumPut(32, bi, 14, &quot;UShort&quot;)
        NumPut(0, bi, 16, &quot;UInt&quot;)

        this.hbm := DllCall(&quot;CreateDIBSection&quot;, &quot;Ptr&quot;, this.hdcMem, &quot;Ptr&quot;, &amp;bi
            , &quot;UInt&quot;, 0, &quot;Ptr*&quot;, pBits, &quot;Ptr&quot;, 0, &quot;UInt&quot;, 0, &quot;Ptr&quot;)
        this.pBits := pBits
        this.oldBm := DllCall(&quot;SelectObject&quot;, &quot;Ptr&quot;, this.hdcMem, &quot;Ptr&quot;, this.hbm, &quot;Ptr&quot;)

        DllCall(&quot;gdiplus\GdipCreateBitmapFromScan0&quot;, &quot;Int&quot;, this.w, &quot;Int&quot;, this.h
            , &quot;Int&quot;, this.w * 4, &quot;Int&quot;, 0xE200B, &quot;Ptr&quot;, this.pBits, &quot;Ptr*&quot;, pBitmap)
        this.pBitmap := pBitmap
        DllCall(&quot;gdiplus\GdipGetImageGraphicsContext&quot;, &quot;Ptr&quot;, this.pBitmap, &quot;Ptr*&quot;, pGraphics)
        this.pGraphics := pGraphics
        DllCall(&quot;gdiplus\GdipSetCompositingMode&quot;, &quot;Ptr&quot;, this.pGraphics, &quot;Int&quot;, 1)
        DllCall(&quot;gdiplus\GdipSetTextRenderingHint&quot;, &quot;Ptr&quot;, this.pGraphics, &quot;Int&quot;, 4)
        DllCall(&quot;gdiplus\GdipSetSmoothingMode&quot;, &quot;Ptr&quot;, this.pGraphics, &quot;Int&quot;, 3)

        DllCall(&quot;gdiplus\GdipCreateSolidFill&quot;, &quot;UInt&quot;, this.color, &quot;Ptr*&quot;, pBrush)
        this.pBrush := pBrush
        DllCall(&quot;gdiplus\GdipCreateSolidFill&quot;, &quot;UInt&quot;, 0x01000000, &quot;Ptr*&quot;, pErase)
        this.pErase := pErase
        DllCall(&quot;gdiplus\GdipGraphicsClear&quot;, &quot;Ptr&quot;, this.pGraphics, &quot;UInt&quot;, 0x01000000)
    }

    Show() {
        options := &quot;NA x0 y0 w&quot; . this.w . &quot; h&quot; . this.h
		Gui, % this.guiName . &quot;:Show&quot;, % options
    }

    Hide() {
        if (this.disposed)
            return
        DllCall(&quot;gdiplus\GdipGraphicsClear&quot;, &quot;Ptr&quot;, this.pGraphics, &quot;UInt&quot;, 0x01000000)
        this.prev := &quot;&quot;
        this.Flush()
		Gui, % this.guiName . &quot;:Hide&quot;
		DllCall(&quot;SetCursor&quot;, &quot;Ptr&quot;, this.hArrow)
    }

    ClearFrame() {
        DllCall(&quot;gdiplus\GdipGraphicsClear&quot;, &quot;Ptr&quot;, this.pGraphics, &quot;UInt&quot;, 0x01000000)
        this.prev := &quot;&quot;
        this.Flush()
    }

    SetRect(x, y, w, h) {
        cur := {x: x, y: y, w: w, h: h}
        if IsObject(this.prev)
            this.Strips(this.prev, this.pErase)
        this.Strips(cur, this.pBrush)
        this.prev := cur
        this.Flush()
    }

    Render(rect, labels) {
        for index, label in labels
            label.Erase(this.pGraphics, this.pErase)
        if IsObject(this.prev)
            this.Strips(this.prev, this.pErase)

        cur := {x: rect.x, y: rect.y, w: rect.w, h: rect.h}
        this.Strips(cur, this.pBrush)
        this.prev := cur
        for index, label in labels
            label.Draw(this.pGraphics, this.w, this.h)

        this.Flush()
        for index, label in labels
            label.Commit()
    }

    RenderLabels(labels) {
        for index, label in labels
            label.Erase(this.pGraphics, this.pErase)
        for index, label in labels
            label.Draw(this.pGraphics, this.w, this.h)

        this.Flush()
        for index, label in labels
            label.Commit()
    }

    PhysT() {
        return Max(1, Round(this.t * this.scale))
    }

    Strips(r, brush) {
        t := this.PhysT()
        this.Fill(brush, r.x - t, r.y - t, r.w + 2 * t, t)
        this.Fill(brush, r.x - t, r.y + r.h, r.w + 2 * t, t)
        this.Fill(brush, r.x - t, r.y, t, r.h)
        this.Fill(brush, r.x + r.w, r.y, t, r.h)
    }

    Fill(brush, x, y, w, h) {
        if (w &lt; 1 || h &lt; 1)
            return
        DllCall(&quot;gdiplus\GdipFillRectangle&quot;, &quot;Ptr&quot;, this.pGraphics
            , &quot;Ptr&quot;, brush, &quot;Float&quot;, x, &quot;Float&quot;, y, &quot;Float&quot;, w, &quot;Float&quot;, h)
    }

    Flush() {
        VarSetCapacity(pt, 8, 0)
        VarSetCapacity(sz, 8, 0)
        NumPut(this.w, sz, 0, &quot;Int&quot;)
        NumPut(this.h, sz, 4, &quot;Int&quot;)
        VarSetCapacity(src, 8, 0)
        VarSetCapacity(blend, 4, 0)
        NumPut(255, blend, 2, &quot;UChar&quot;)
        NumPut(1, blend, 3, &quot;UChar&quot;)

        DllCall(&quot;UpdateLayeredWindow&quot;, &quot;Ptr&quot;, this.hwnd, &quot;Ptr&quot;, 0
            , &quot;Ptr&quot;, &amp;pt, &quot;Ptr&quot;, &amp;sz, &quot;Ptr&quot;, this.hdcMem, &quot;Ptr&quot;, &amp;src
            , &quot;UInt&quot;, 0, &quot;Ptr&quot;, &amp;blend, &quot;UInt&quot;, 2)
    }

    Dispose() {
        if (this.disposed)
            return
        this.disposed := true

        if (this.pBrush)
            DllCall(&quot;gdiplus\GdipDeleteBrush&quot;, &quot;Ptr&quot;, this.pBrush)
        if (this.pErase)
            DllCall(&quot;gdiplus\GdipDeleteBrush&quot;, &quot;Ptr&quot;, this.pErase)
        if (this.pGraphics)
            DllCall(&quot;gdiplus\GdipDeleteGraphics&quot;, &quot;Ptr&quot;, this.pGraphics)
        if (this.pBitmap)
            DllCall(&quot;gdiplus\GdipDisposeImage&quot;, &quot;Ptr&quot;, this.pBitmap)
        if (this.hbm) {
            DllCall(&quot;SelectObject&quot;, &quot;Ptr&quot;, this.hdcMem, &quot;Ptr&quot;, this.oldBm)
            DllCall(&quot;DeleteObject&quot;, &quot;Ptr&quot;, this.hbm)
        }
        if (this.hdcMem)
            DllCall(&quot;DeleteDC&quot;, &quot;Ptr&quot;, this.hdcMem)
		if (this.hwnd &amp;&amp; DllCall(&quot;IsWindow&quot;, &quot;Ptr&quot;, this.hwnd))
			Gui, % this.guiName . &quot;:Destroy&quot;

        this.pBrush := 0
        this.pErase := 0
        this.pGraphics := 0
        this.pBitmap := 0
        this.hbm := 0
        this.hdcMem := 0
		this.hArrow := 0
        this.hwnd := 0
    }

    __Delete() {
        this.Dispose()
    }
}

class SelectionOverlayLabel {
    __New(style := &quot;&quot;, scale := 1) {
        if !IsObject(style)
            style := {}

        fontName := this.StyleValue(style, &quot;fontName&quot;, &quot;Segoe UI&quot;)
        fontSize := this.StyleValue(style, &quot;fontSize&quot;, 14) * scale
        fontStyle := this.StyleValue(style, &quot;fontStyle&quot;, 0)
        textColor := this.StyleValue(style, &quot;textColor&quot;, 0xFFFFFF)
        backColor := this.StyleValue(style, &quot;backColor&quot;, 0x202020)
        this.padX := Round(this.StyleValue(style, &quot;padX&quot;, 6) * scale)
        this.padY := Round(this.StyleValue(style, &quot;padY&quot;, 3) * scale)
        this.radius := Round(this.StyleValue(style, &quot;radius&quot;, 6) * scale)
        this.text := &quot;&quot;
        this.box := &quot;&quot;
        this.eraseBox := &quot;&quot;
        this.prevErase := &quot;&quot;
        this.disposed := false

        DllCall(&quot;gdiplus\GdipCreateFontFamilyFromName&quot;, &quot;WStr&quot;, fontName
            , &quot;Ptr&quot;, 0, &quot;Ptr*&quot;, family)
        this.family := family
        DllCall(&quot;gdiplus\GdipCreateFont&quot;, &quot;Ptr&quot;, this.family, &quot;Float&quot;, fontSize
            , &quot;Int&quot;, fontStyle, &quot;Int&quot;, 2, &quot;Ptr*&quot;, font)
        this.font := font

        DllCall(&quot;gdiplus\GdipCreateStringFormat&quot;, &quot;Int&quot;, 0, &quot;Int&quot;, 0, &quot;Ptr*&quot;, fmt)
        this.fmt := fmt
        DllCall(&quot;gdiplus\GdipSetStringFormatAlign&quot;, &quot;Ptr&quot;, this.fmt, &quot;Int&quot;, 0)
        DllCall(&quot;gdiplus\GdipSetStringFormatLineAlign&quot;, &quot;Ptr&quot;, this.fmt, &quot;Int&quot;, 1)

        DllCall(&quot;gdiplus\GdipCreateSolidFill&quot;, &quot;UInt&quot;, 0xFF000000 | textColor, &quot;Ptr*&quot;, textBrush)
        this.textBrush := textBrush
        DllCall(&quot;gdiplus\GdipCreateSolidFill&quot;, &quot;UInt&quot;, 0xFF000000 | backColor, &quot;Ptr*&quot;, backBrush)
        this.backBrush := backBrush
    }

    StyleValue(style, key, defaultValue) {
        return style.HasKey(key) ? style[key] : defaultValue
    }

    SetText(text, anchorX, anchorY, side := &quot;top&quot;) {
        this.text := text
        this.ax := anchorX
        this.ay := anchorY
        this.side := side
    }

    Measure(pGraphics) {
        VarSetCapacity(layout, 16, 0)
        NumPut(0, layout, 0, &quot;Float&quot;)
        NumPut(0, layout, 4, &quot;Float&quot;)
        NumPut(9999, layout, 8, &quot;Float&quot;)
        NumPut(9999, layout, 12, &quot;Float&quot;)
        VarSetCapacity(bounds, 16, 0)
        DllCall(&quot;gdiplus\GdipMeasureString&quot;, &quot;Ptr&quot;, pGraphics, &quot;WStr&quot;, this.text
            , &quot;Int&quot;, -1, &quot;Ptr&quot;, this.font, &quot;Ptr&quot;, &amp;layout, &quot;Ptr&quot;, this.fmt
            , &quot;Ptr&quot;, &amp;bounds, &quot;Int*&quot;, chars, &quot;Int*&quot;, lines)
        return {w: NumGet(bounds, 8, &quot;Float&quot;), h: NumGet(bounds, 12, &quot;Float&quot;)}
    }

    ComputeBox(pGraphics, screenW, screenH) {
        metrics := this.Measure(pGraphics)
        bw := Ceil(metrics.w) + this.padX * 2
        bh := Ceil(metrics.h) + this.padY * 2

        if (this.side = &quot;top&quot;) {
            bx := this.ax
            by := this.ay - bh - 1
        } else if (this.side = &quot;bottom&quot;) {
            bx := this.ax - bw
            by := this.ay
        } else if (this.side = &quot;left&quot;) {
            bx := this.ax - bw
            by := this.ay - bh // 2
        } else if (this.side = &quot;right&quot;) {
            bx := this.ax
            by := this.ay - bh // 2
        } else if (this.side = &quot;topLeft&quot;) {
            bx := this.ax - bw
            by := this.ay - bh - 1
        } else {
            bx := this.ax - bw // 2
            by := this.ay - bh // 2
        }

        bx := Max(0, Min(bx, screenW - bw))
        by := Max(0, Min(by, screenH - bh))
        return {x: bx, y: by, w: bw, h: bh}
    }

    Draw(pGraphics, screenW, screenH) {
        if (this.text = &quot;&quot;) {
            this.box := &quot;&quot;
            this.eraseBox := &quot;&quot;
            return
        }

        box := this.ComputeBox(pGraphics, screenW, screenH)
        this.box := box
        ex := Max(0, box.x - 1)
        ey := Max(0, box.y - 1)
        er := Min(screenW, box.x + box.w + 1)
        eb := Min(screenH, box.y + box.h + 1)
        this.eraseBox := {x: ex, y: ey, w: er - ex, h: eb - ey}

        DllCall(&quot;gdiplus\GdipSetCompositingMode&quot;, &quot;Ptr&quot;, pGraphics, &quot;Int&quot;, 0)
        this.FillRoundRect(pGraphics, box.x, box.y, box.w, box.h, this.radius, this.backBrush)

        VarSetCapacity(rect, 16, 0)
        NumPut(box.x + this.padX, rect, 0, &quot;Float&quot;)
        NumPut(box.y, rect, 4, &quot;Float&quot;)
        NumPut(box.w, rect, 8, &quot;Float&quot;)
        NumPut(box.h, rect, 12, &quot;Float&quot;)
        DllCall(&quot;gdiplus\GdipDrawString&quot;, &quot;Ptr&quot;, pGraphics, &quot;WStr&quot;, this.text
            , &quot;Int&quot;, -1, &quot;Ptr&quot;, this.font, &quot;Ptr&quot;, &amp;rect, &quot;Ptr&quot;, this.fmt
            , &quot;Ptr&quot;, this.textBrush)
        DllCall(&quot;gdiplus\GdipSetCompositingMode&quot;, &quot;Ptr&quot;, pGraphics, &quot;Int&quot;, 1)
    }

    FillRoundRect(pGraphics, x, y, w, h, radius, brush) {
        radius := Min(radius, Min(w // 2, h // 2))
        diameter := radius * 2
        DllCall(&quot;gdiplus\GdipCreatePath&quot;, &quot;Int&quot;, 0, &quot;Ptr*&quot;, path)
        if (radius &gt; 0) {
            DllCall(&quot;gdiplus\GdipAddPathArc&quot;, &quot;Ptr&quot;, path, &quot;Float&quot;, x
                , &quot;Float&quot;, y, &quot;Float&quot;, diameter, &quot;Float&quot;, diameter, &quot;Float&quot;, 180, &quot;Float&quot;, 90)
            DllCall(&quot;gdiplus\GdipAddPathArc&quot;, &quot;Ptr&quot;, path, &quot;Float&quot;, x + w - diameter
                , &quot;Float&quot;, y, &quot;Float&quot;, diameter, &quot;Float&quot;, diameter, &quot;Float&quot;, 270, &quot;Float&quot;, 90)
            DllCall(&quot;gdiplus\GdipAddPathArc&quot;, &quot;Ptr&quot;, path, &quot;Float&quot;, x + w - diameter
                , &quot;Float&quot;, y + h - diameter, &quot;Float&quot;, diameter, &quot;Float&quot;, diameter, &quot;Float&quot;, 0, &quot;Float&quot;, 90)
            DllCall(&quot;gdiplus\GdipAddPathArc&quot;, &quot;Ptr&quot;, path, &quot;Float&quot;, x
                , &quot;Float&quot;, y + h - diameter, &quot;Float&quot;, diameter, &quot;Float&quot;, diameter, &quot;Float&quot;, 90, &quot;Float&quot;, 90)
            DllCall(&quot;gdiplus\GdipClosePathFigure&quot;, &quot;Ptr&quot;, path)
        } else {
            DllCall(&quot;gdiplus\GdipAddPathRectangle&quot;, &quot;Ptr&quot;, path
                , &quot;Float&quot;, x, &quot;Float&quot;, y, &quot;Float&quot;, w, &quot;Float&quot;, h)
        }

        DllCall(&quot;gdiplus\GdipSetSmoothingMode&quot;, &quot;Ptr&quot;, pGraphics, &quot;Int&quot;, 4)
        DllCall(&quot;gdiplus\GdipFillPath&quot;, &quot;Ptr&quot;, pGraphics, &quot;Ptr&quot;, brush, &quot;Ptr&quot;, path)
        DllCall(&quot;gdiplus\GdipSetSmoothingMode&quot;, &quot;Ptr&quot;, pGraphics, &quot;Int&quot;, 3)
        DllCall(&quot;gdiplus\GdipDeletePath&quot;, &quot;Ptr&quot;, path)
    }

    Erase(pGraphics, eraseBrush) {
        if !IsObject(this.prevErase)
            return
        box := this.prevErase
        DllCall(&quot;gdiplus\GdipFillRectangle&quot;, &quot;Ptr&quot;, pGraphics, &quot;Ptr&quot;, eraseBrush
            , &quot;Float&quot;, box.x, &quot;Float&quot;, box.y, &quot;Float&quot;, box.w, &quot;Float&quot;, box.h)
    }

    Commit() {
        this.prevErase := this.eraseBox
    }

    Dispose() {
        if (this.disposed)
            return
        this.disposed := true

        if (this.textBrush)
            DllCall(&quot;gdiplus\GdipDeleteBrush&quot;, &quot;Ptr&quot;, this.textBrush)
        if (this.backBrush)
            DllCall(&quot;gdiplus\GdipDeleteBrush&quot;, &quot;Ptr&quot;, this.backBrush)
        if (this.fmt)
            DllCall(&quot;gdiplus\GdipDeleteStringFormat&quot;, &quot;Ptr&quot;, this.fmt)
        if (this.font)
            DllCall(&quot;gdiplus\GdipDeleteFont&quot;, &quot;Ptr&quot;, this.font)
        if (this.family)
            DllCall(&quot;gdiplus\GdipDeleteFontFamily&quot;, &quot;Ptr&quot;, this.family)

        this.textBrush := 0
        this.backBrush := 0
        this.fmt := 0
        this.font := 0
        this.family := 0
    }

    __Delete() {
        this.Dispose()
    }
}

class ToolTip {
    ; Default options. Any option omitted by the caller falls back to the value here.
    ; Color options use &quot;&quot; to mean &quot;leave the system default&quot; (0 is a valid black).
    DefaultOptions() {
        return { text: &quot;&quot;, title: &quot;&quot;, icon: 0
               , x: &quot;&quot;, y: &quot;&quot;, TimeOut: &quot;&quot;, ShowNow: true
               , FontName: &quot;&quot;, FontSize: 0, FontStyle: &quot;&quot;
               , BackColor: &quot;&quot;, TextColor: &quot;&quot;, Margin: &quot;&quot;
               , BalloonTip: false, CloseButton: false
			   , transparent: true, RightBottom: false, TrayTip: false }
    }

    __New( options ) {
        defaults := this.DefaultOptions()
        for k, v in defaults
            this[k] := options.HasKey(k) ? options[k] : v
        this._CreateToolTip()
        ( this.ShowNow &amp;&amp; this.Show(this.x, this.y, this.TimeOut) )
    }
    
    _CreateToolTip() {
        static WS_POPUP := 0x80000000, WS_EX_TOPMOST := 8, WS_EX_TRANSPARENT := 0x20
              , TTS_NOPREFIX := 2, TTS_ALWAYSTIP := 1, TTS_BALLOON := 0x40, TTS_CLOSE := 0x80
              , TTF_TRACK := 0x20, TTF_ABSOLUTE := 0x80, szTI := A_PtrSize = 4 ? 48 : 72
      
        VarSetCapacity(TOOLINFO, szTI, 0)
        this.pTI := &amp;TOOLINFO
        NumPut(szTI, TOOLINFO)
        (this.TrayTip &amp;&amp; this.BalloonTip := true)
        (this.CloseButton &amp;&amp; this.transparent := false)
        NumPut(TTF_TRACK|(this.BalloonTip ? 0 : TTF_ABSOLUTE), &amp;TOOLINFO + 4)
        
        this.hwnd := DllCall(&quot;CreateWindowEx&quot;, &quot;UInt&quot;, WS_EX_TOPMOST|(this.transparent ? WS_EX_TRANSPARENT : 0)
          , &quot;Str&quot;, &quot;tooltips_class32&quot;, &quot;Str&quot;, &quot;&quot;
          , &quot;UInt&quot;, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | (this.CloseButton ? TTS_CLOSE : 0) | (this.BalloonTip ? TTS_BALLOON : 0)
          , &quot;Int&quot;, 0, &quot;Int&quot;, 0, &quot;Int&quot;, 0, &quot;Int&quot;, 0, &quot;Ptr&quot;, 0, &quot;Ptr&quot;, 0, &quot;Ptr&quot;, 0, &quot;Ptr&quot;, 0)
          
        if (this.FontName || this.FontSize || this.FontStyle)
            this._SetFont()
        
        if (this.TextColor != &quot;&quot; || this.BackColor != &quot;&quot;)
            this._SetColor()
        
        this._SetInfo()
    }
    
    _SetFont() {
        static WM_GETFONT := 0x31, mult := A_IsUnicode ? 2 : 1, szLF := 28 + 32 * mult
             , LOGPIXELSY := 90, ANTIALIASED_QUALITY := 4
             , styles := { bold: {value: 700, offset: 16, size: &quot;Int&quot;}
                         , italic: {value: 1, offset: 20, size: &quot;Char&quot;}
                         , underline: {value: 1, offset: 21, size: &quot;Char&quot;}
                         , strikeout: {value: 1, offset: 22, size: &quot;Char&quot;} }
                              
        hPrevFont := this._SendMessage(WM_GETFONT)
        VarSetCapacity(LOGFONT, szLF, 0)
        DllCall(&quot;GetObject&quot;, &quot;Ptr&quot;, hPrevFont, &quot;Int&quot;, szLF, &quot;Ptr&quot;, &amp;LOGFONT)
        DllCall(&quot;DeleteObject&quot;, &quot;Ptr&quot;, hPrevFont)
        
        if this.FontSize {
            hDC := DllCall(&quot;GetDC&quot;, &quot;Ptr&quot;, this.hwnd, &quot;Ptr&quot;)
			picH := DllCall(&quot;GetDeviceCaps&quot;, &quot;Ptr&quot;, hDC, &quot;Int&quot;, LOGPIXELSY)
            height := -DllCall(&quot;MulDiv&quot;, &quot;Int&quot;, this.FontSize, &quot;Int&quot;, picH, &quot;Int&quot;, 72)
            DllCall(&quot;ReleaseDC&quot;, &quot;Ptr&quot;, this.hwnd, &quot;Ptr&quot;, hDC)
            NumPut(height, LOGFONT, &quot;Int&quot;)
        }
        FontStyle := this.FontStyle
        Loop, parse, FontStyle, %A_Space%
		{
            if obj := styles[A_LoopField]
                NumPut(obj.value, &amp;LOGFONT + obj.offset, obj.size)
		}
        (this.FontSize &gt; 24 &amp;&amp; NumPut(ANTIALIASED_QUALITY, &amp;LOGFONT + 26, &quot;Char&quot;))
        this.FontName &amp;&amp; StrPut(this.FontName, &amp;LOGFONT + 28, StrLen(this.FontName) * mult, A_IsUnicode ? &quot;UTF-16&quot; : &quot;CP0&quot;)
        this.hFont := DllCall(&quot;CreateFontIndirect&quot;, &quot;Ptr&quot;, &amp;LOGFONT, &quot;Ptr&quot;)
    }
    
    _SetColor() {
        static WM_USER := 0x400, TTM_SETTIPBKCOLOR := WM_USER + 19, TTM_SETTIPTEXTCOLOR := WM_USER + 20
        
        VarSetCapacity(empty, 2, 0)
        DllCall(&quot;UxTheme\SetWindowTheme&quot;, &quot;Ptr&quot;, this.hwnd, &quot;Ptr&quot;, 0, &quot;Ptr&quot;, &amp;empty)   
        ( this.TextColor != &quot;&quot; &amp;&amp; this._SendMessage(TTM_SETTIPTEXTCOLOR, this._GetColor(this.TextColor)) )
        ( this.BackColor != &quot;&quot; &amp;&amp; this._SendMessage(TTM_SETTIPBKCOLOR, this._GetColor(this.BackColor)) )
    }
    
    _GetColor(color) {
        static WS_CHILD := 0x40000000, WM_CTLCOLORSTATIC := 0x138
        
        Gui, New, +hwndhGui +%WS_CHILD%
        Gui, Color, % color + 0 = &quot;&quot; ? color : Format(&quot;{:x}&quot;, color)
        Gui, Add, Text, hwndhText
        hdc := DllCall(&quot;GetDC&quot;, &quot;Ptr&quot;, hText, &quot;Ptr&quot;)
        SendMessage, WM_CTLCOLORSTATIC, hdc, hText,, ahk_id %hGui%
        clr := DllCall(&quot;GetBkColor&quot;, &quot;Ptr&quot;, hdc)
        DllCall(&quot;ReleaseDC&quot;, &quot;Ptr&quot;, hText, &quot;Ptr&quot;, hdc)
        Gui, Destroy
        return clr
    }
    
    _SetInfo() {
        static WM_USER := 0x400, TTM_SETMAXTIPWIDTH := WM_USER + 24
              , TTM_ADDTOOL := WM_USER + (A_IsUnicode ? 50 : 4), WM_SETFONT := 0x30
        
        this._SendMessage(WM_SETFONT, this.hFont, 1)
        this._SendMessage(TTM_ADDTOOL, 0, this.pTI)
        this.SetTitle(this.icon, this.title)
        this._SendMessage(TTM_SETMAXTIPWIDTH, 0, A_ScreenWidth)
        this._SetMargin()
        this.SetText(this.text)
    }
    
    _SetMargin() {
        static WM_USER := 0x400, TTM_SETMARGIN := WM_USER + 26
        
        margin := this.Margin
        if !(IsObject(margin) &amp;&amp; margin.MaxIndex() = 4)
            return
        VarSetCapacity(RECT, 16, 0)
        for i, offset in margin
            NumPut(offset, &amp;RECT + (i - 1) * 4, &quot;Int&quot;)
        this._SendMessage(TTM_SETMARGIN, 0, &amp;RECT)
    }
    
    SetText(text) {
        static WM_USER := 0x400, TTM_UPDATETIPTEXT := WM_USER + (A_IsUnicode ? 57 : 12)
        NumPut(&amp;text, this.pTI + (A_PtrSize = 4 ? 36 : 48))
        this._SendMessage(TTM_UPDATETIPTEXT, 0, this.pTI)
    }
    
    SetTitle(icon := &quot;&quot;, title := &quot;&quot;) {
        static WM_USER := 0x400, TTM_SETTITLE := WM_USER + (A_IsUnicode ? 33 : 32), TTM_UPDATE := WM_USER + 29
        
        ((icon || this.CloseButton) &amp;&amp; title = &quot;&quot;) &amp;&amp; title := &quot; &quot;
        this._SendMessage(TTM_SETTITLE, icon, &amp;title)
        (icon &gt; 3 &amp;&amp; DllCall(&quot;DestroyIcon&quot;, &quot;Ptr&quot;, icon))
        this._SendMessage(TTM_UPDATE)
    }
    
    Show(x := &quot;&quot;, y := &quot;&quot;, timeout := &quot;&quot;) {
        static WM_USER := 0x400, TTM_TRACKACTIVATE := WM_USER + 17, TTM_TRACKPOSITION := WM_USER + 18
        
        SysGet, MonPrimary, MonitorPrimary
        SysGet, Mon, MonitorWorkArea, %MonPrimary%
        xTT := x, yTT := y
        
        ; TrayTip mode: anchor the balloon to the script&#039;s tray icon (one-shot)
        if this.TrayTip {
            this._GetTrayIconCoords(xTT, yTT)
        ; RightBottom mode: push beyond the work area so the post-activation
        ; clamp below pins the tip to the bottom-right corner, above the taskbar
        } else if this.RightBottom {
            xTT := MonRight, yTT := MonBottom
        } else if (xTT = &quot;&quot; || yTT = &quot;&quot;) {
            CoordMode, Mouse
            MouseGetPos, xm, ym
            (xTT = &quot;&quot; &amp;&amp; xTT := xm + 10)
            (yTT = &quot;&quot; &amp;&amp; yTT := ym + 10)
        }
        
        this._SendMessage(TTM_TRACKPOSITION, 0, xTT|(yTT&lt;&lt;16))
        this._SendMessage(TTM_TRACKACTIVATE, 1, this.pTI)
        
        if this.BalloonTip
            xMax := MonRight, yMax := MonBottom
        else {
            WinGetPos,,, W, H, % &quot;ahk_id&quot; this.hwnd
            xMax := MonRight - W - 10
            yMax := MonBottom - H - 10
        }
        
        if (xTT &gt; xMax || yTT &gt; yMax) {
            (xTT &gt; xMax &amp;&amp; xTT := xMax)
            (yTT &gt; yMax &amp;&amp; yTT := yMax)
            this._SendMessage(TTM_TRACKPOSITION, 0, xTT|(yTT&lt;&lt;16))
        }

        if timeout {
            timer := this.timer
            try SetTimer, % timer, Delete
            this.timer := timer := ObjBindMethod(this, timeout &gt; 0 ? &quot;Hide&quot; : &quot;Destroy&quot;)
            SetTimer % timer, % &quot;-&quot; . Abs(timeout)
        }
    }
    
    Hide() {
        static WM_USER := 0x400, TTM_TRACKACTIVATE := WM_USER + 17
        this._SendMessage(TTM_TRACKACTIVATE, 0, this.pTI)
    }
    
    Destroy() {
        this.__Delete()
        this.SetCapacity(0)
        this.base := &quot;&quot;
    }
    
    _SendMessage(msg, wp := 0, lp := 0) {
        return DllCall(&quot;SendMessage&quot;, &quot;Ptr&quot;, this.hwnd, &quot;UInt&quot;, msg, &quot;Ptr&quot;, wp, &quot;Ptr&quot;, lp, &quot;Ptr&quot;)
    }
    
    _GetTrayIconCoords(ByRef x, ByRef y) {
        static AHK_NOTIFYICON := 0x404, szNII := 16 + 3 * A_PtrSize
        
        ; Modern, Win11-safe way: ask the shell for the icon&#039;s screen rect directly.
        VarSetCapacity(NII, szNII, 0)
        NumPut(szNII, NII, 0, &quot;UInt&quot;)
        NumPut(A_ScriptHwnd, NII, A_PtrSize, &quot;Ptr&quot;)
        NumPut(AHK_NOTIFYICON, NII, A_PtrSize * 2, &quot;UInt&quot;)
        VarSetCapacity(RECT, 16, 0)
        
        if (DllCall(&quot;Shell32\Shell_NotifyIconGetRect&quot;, &quot;Ptr&quot;, &amp;NII, &quot;Ptr&quot;, &amp;RECT, &quot;UInt&quot;) = 0) {
            xL := NumGet(RECT, 0, &quot;Int&quot;), yT := NumGet(RECT, 4, &quot;Int&quot;)
            xR := NumGet(RECT, 8, &quot;Int&quot;)
            x := (xL + xR) // 2   ; balloon anchors its tail at this point
            y := yT
        } else {
            ; Fallback: aim at the tray notification area as a whole.
            for k, v in [&quot;TrayNotifyWnd&quot;, &quot;SysPager&quot;, &quot;ToolbarWindow32&quot;]
                hTray := DllCall(&quot;FindWindowEx&quot;, &quot;Ptr&quot;, k = 1 ? WinExist(&quot;ahk_class Shell_TrayWnd&quot;) : hTray, &quot;Ptr&quot;, 0, &quot;Str&quot;, v, &quot;UInt&quot;, 0, &quot;Ptr&quot;)
            WinGetPos, xTray, yTray, wTray,, % &quot;ahk_id&quot; hTray
            x := xTray + wTray // 2, y := yTray
        }
    }
    
    __Delete() {
        (this.hFont &amp;&amp; DllCall(&quot;DeleteObject&quot;, &quot;Ptr&quot;, this.hFont))
        (this.Icon &gt; 3 &amp;&amp; DllCall(&quot;DestroyIcon&quot;, &quot;Ptr&quot;, this.Icon))
        DllCall(&quot;DestroyWindow&quot;, &quot;Ptr&quot;, this.hwnd)
    }
}

;*************************************************************** HotKeys ***************************************************************

;  Автор - serzh82saratov
;  E-Mail: serzh82saratov@mail.ru
;  Описание - http://forum.script-coding.com/viewtopic.php?id=8343

Hotkey_Register(Controls*) {
    static IsStart
    for k, v in Controls
    {
        Hotkey_ID(v[2], v[1]), Hotkey_ID(v[1], v[2])
        Hotkey_Options(v[2], v[3] = &quot;&quot; ? &quot;K&quot; : v[3])
        Hotkey_Value(v[2], Hotkey_Value(v[1]))
        PostMessage, 0x00CF, 1, , , % &quot;ahk_id&quot; v[2]   ;  EM_SETREADONLY
        if RegExMatch(v[3], &quot;i)G(\d+)&quot;, g)
            Hotkey_Group(&quot;Set&quot;, v[1], g1)
    }
    if IsStart
        return Hotkey_IsRegFocus()
    #HotkeyInterval 0
    Hotkey_SetWinEventHook(0x8005, 0x8005, 0, RegisterCallback(&quot;Hotkey_EventFocus&quot;, &quot;F&quot;), 0, 0, 0)   ;  EVENT_OBJECT_FOCUS := 0x8005
    Hotkey_InitHotkeys(), Hotkey_IsRegFocus(), IsStart := 1
}

Hotkey_Main(Param1, Param2 = &quot;&quot;) {
    static OnlyMods, ControlHandle, Hotkey, KeyName, K := {}
    , Prefix := {&quot;LAlt&quot;:&quot;&lt;!&quot;,&quot;LCtrl&quot;:&quot;&lt;^&quot;,&quot;LShift&quot;:&quot;&lt;+&quot;,&quot;LWin&quot;:&quot;&lt;#&quot;
                ,&quot;RAlt&quot;:&quot;&gt;!&quot;,&quot;RCtrl&quot;:&quot;&gt;^&quot;,&quot;RShift&quot;:&quot;&gt;+&quot;,&quot;RWin&quot;:&quot;&gt;#&quot;
                ,&quot;Alt&quot;:&quot;!&quot;,&quot;Ctrl&quot;:&quot;^&quot;,&quot;Shift&quot;:&quot;+&quot;,&quot;Win&quot;:&quot;#&quot;}
    , EngSym := {&quot;sc2&quot;:&quot;1&quot;,&quot;sc3&quot;:&quot;2&quot;,&quot;sc4&quot;:&quot;3&quot;,&quot;sc5&quot;:&quot;4&quot;,&quot;sc6&quot;:&quot;5&quot;,&quot;sc7&quot;:&quot;6&quot;
                ,&quot;sc8&quot;:&quot;7&quot;,&quot;sc9&quot;:&quot;8&quot;,&quot;scA&quot;:&quot;9&quot;,&quot;scB&quot;:&quot;0&quot;,&quot;scC&quot;:&quot;-&quot;,&quot;scD&quot;:&quot;=&quot;
                ,&quot;sc10&quot;:&quot;Q&quot;,&quot;sc11&quot;:&quot;W&quot;,&quot;sc12&quot;:&quot;E&quot;,&quot;sc13&quot;:&quot;R&quot;,&quot;sc14&quot;:&quot;T&quot;,&quot;sc15&quot;:&quot;Y&quot;
                ,&quot;sc16&quot;:&quot;U&quot;,&quot;sc17&quot;:&quot;I&quot;,&quot;sc18&quot;:&quot;O&quot;,&quot;sc19&quot;:&quot;P&quot;,&quot;sc1A&quot;:&quot;[&quot;,&quot;sc1B&quot;:&quot;]&quot;
                ,&quot;sc1E&quot;:&quot;A&quot;,&quot;sc1F&quot;:&quot;S&quot;,&quot;sc20&quot;:&quot;D&quot;,&quot;sc21&quot;:&quot;F&quot;,&quot;sc22&quot;:&quot;G&quot;,&quot;sc23&quot;:&quot;H&quot;
                ,&quot;sc24&quot;:&quot;J&quot;,&quot;sc25&quot;:&quot;K&quot;,&quot;sc26&quot;:&quot;L&quot;,&quot;sc27&quot;:&quot;`;&quot;,&quot;sc28&quot;:&quot;&#039;&quot;,&quot;sc29&quot;:&quot;``&quot;
                ,&quot;sc2B&quot;:&quot;\&quot;,&quot;sc2C&quot;:&quot;Z&quot;,&quot;sc2D&quot;:&quot;X&quot;,&quot;sc2E&quot;:&quot;C&quot;,&quot;sc2F&quot;:&quot;V&quot;,&quot;sc30&quot;:&quot;B&quot;
                ,&quot;sc31&quot;:&quot;N&quot;,&quot;sc32&quot;:&quot;M&quot;,&quot;sc33&quot;:&quot;,&quot;,&quot;sc34&quot;:&quot;.&quot;,&quot;sc35&quot;:&quot;/&quot;}

    if Param1 = GetMod
        return K.MLCtrl K.MRCtrl K.MLAlt K.MRAlt K.MLShift K.MRShift K.MLWin K.MRWin K.MCtrl K.MAlt K.MShift K.MWin
    if Param2
    {
        if OnlyMods
        {
            SendMessage, 0xC, 0, &quot;&quot; Hotkey_Arr(&quot;Empty&quot;), , ahk_id %ControlHandle%
            OnlyMods := 0, K := {}
        }
        ControlHandle := Param2
        Hotkey_Arr(&quot;Hook&quot;, Hotkey_Options(ControlHandle))
        PostMessage, 0x00B1, -1, -1, , ahk_id %ControlHandle%   ;  EM_SETSEL
    }
    else if Hotkey_Arr(&quot;Hook&quot;)
    {
        Hotkey_Arr(&quot;Hook&quot;, 0), K := {}
        if OnlyMods
            SendMessage, 0xC, 0, &quot;&quot; Hotkey_Arr(&quot;Empty&quot;), , ahk_id %ControlHandle%
        SetTimer, Hotkey_IsRegFocus, -200
    }
    return

Hotkey_Mods:
    if InStr(Hotkey_Arr(&quot;Hook&quot;), &quot;S&quot;)
        GoTo Hotkey_View
    IsMod := InStr(Hotkey_Arr(&quot;Hook&quot;), &quot;D&quot;) ? A_ThisHotkey : SubStr(A_ThisHotkey, 2)
    if (K[&quot;M&quot; IsMod] != &quot;&quot;)
        return
    K[&quot;M&quot; IsMod] := IsMod &quot;+&quot;, K[&quot;P&quot; IsMod] := Prefix[IsMod]
    GoTo Hotkey_ViewMod

Hotkey_ModsUp:
    if InStr(Hotkey_Arr(&quot;Hook&quot;), &quot;S&quot;)
        return
    IsMod := InStr(Hotkey_Arr(&quot;Hook&quot;), &quot;D&quot;) ? SubStr(A_ThisHotkey, 1, -3) : SubStr(A_ThisHotkey, 2, -3)
    K[&quot;M&quot; IsMod] := &quot;&quot;, K[&quot;P&quot; IsMod] := &quot;&quot;
    if (Hotkey != &quot;&quot;)
        return

Hotkey_ViewMod:
    Hotkey := &quot;&quot;, OnlyMods := 1, Hotkey_Value(Hotkey_ID(ControlHandle), K.Prefix Hotkey), Hotkey_Value(ControlHandle, K.Prefix Hotkey)
    K.Mods := K.MLCtrl K.MRCtrl K.MLAlt K.MRAlt K.MLShift K.MRShift K.MLWin K.MRWin K.MCtrl K.MAlt K.MShift K.MWin
    Text := K.Mods = &quot;&quot; ? Hotkey_Arr(&quot;Empty&quot;) : K.Mods
    SendMessage, 0xC, 0, &amp;Text, , ahk_id %ControlHandle%
    return

Hotkey_ViewNum:
    if InStr(Hotkey_Arr(&quot;Hook&quot;), &quot;N&quot;)
        KeyName := Hotkey := GetKeyName(A_ThisHotkey)
    else
        KeyName := GetKeyName(A_ThisHotkey), Hotkey := Format(&quot;sc{:x}&quot;, GetKeySC(A_ThisHotkey))
    GoTo, Hotkey_Put

Hotkey_ViewSC:
    KeyName := Hotkey_Arr(&quot;OnlyEngSym&quot;) ? EngSym[A_ThisHotkey] : Format(&quot;{:U}&quot;, GetKeyName(A_ThisHotkey))
    Hotkey := A_ThisHotkey
    GoTo, Hotkey_Put

Hotkey_View:
    if (A_ThisHotkey = &quot;Backspace&quot;)
        GoTo, Hotkey_Clear
    if Hotkey_IsMouseInput(A_ThisHotkey)
        Hotkey_SyncPhysicalModifiers(K)
    KeyName := Hotkey := A_ThisHotkey

Hotkey_Put:
    OnlyMods := 0
    K.Prefix := K.PLCtrl K.PRCtrl K.PLAlt K.PRAlt K.PLShift K.PRShift K.PLWin K.PRWin K.PCtrl K.PAlt K.PShift K.PWin
    Hotkey_Value(Hotkey_ID(ControlHandle), K.Prefix Hotkey), Hotkey_Value(ControlHandle, K.Prefix Hotkey)
    K.Mods := K.MLCtrl K.MRCtrl K.MLAlt K.MRAlt K.MLShift K.MRShift K.MLWin K.MRWin K.MCtrl K.MAlt K.MShift K.MWin
    Text := K.Mods KeyName = &quot;&quot; ? Hotkey_Arr(&quot;Empty&quot;) : K.Mods KeyName
    SendMessage, 0xC, 0, &amp;Text, , ahk_id %ControlHandle%
    if Hotkey_Group(&quot;Get&quot;, Hotkey_ID(ControlHandle)) &amp;&amp; Hotkey_Group(&quot;SaveCheck&quot;, ControlHandle)
        SetTimer, Hotkey_Group, -70
    return

Hotkey_Clear:
    OnlyMods := 0, K := {}
    Hotkey_Value(Hotkey_ID(ControlHandle), &quot; &quot;), Hotkey_Value(ControlHandle, &quot; &quot;)
    Text := Hotkey_Arr(&quot;Empty&quot;)
    SendMessage, 0xC, 0, &amp;Text, , ahk_id %ControlHandle%
    if Hotkey_Group(&quot;Get&quot;, Hotkey_ID(ControlHandle)) &amp;&amp; Hotkey_Group(&quot;SaveCheck&quot;, ControlHandle)
        SetTimer, Hotkey_Group, -70
    return
}

Hotkey_InitHotkeys() {
    static nmMods := &quot;LAlt|RAlt|LCtrl|RCtrl|LShift|RShift|LWin|RWin&quot;
    , nmMouse := &quot;MButton|WheelDown|WheelUp|WheelRight|WheelLeft|XButton1|XButton2&quot;
    , scSymb := &quot;2|3|4|5|6|7|8|9|A|B|C|D|10|11|12|13|14|15|16|17|18|19|1A|1B|&quot;
        . &quot;1E|1F|20|21|22|23|24|25|26|27|28|29|2B|2C|2D|2E|2F|30|31|32|33|34|35&quot;
    ; , scNumSymb := &quot;53|52|4F|50|51|4B|4C|4D|47|48|49&quot;
    , scOther := &quot;1|E|F|1C|37|39|3A|3B|3C|3D|3E|3F|40|41|42|43|44|45|46|4A|4E|54|57|58|63|64|65|&quot;
        . &quot;66|67|68|69|6A|6B|6C|6D|6E|76|11C|135|147|148|149|14B|14D|14F|150|151|152|153|15D&quot;
    , vkNum := &quot;2E|6E|60|2D|61|23|62|28|63|22|64|25|65|C|66|27|67|24|68|26|69&quot;
    , vkOther := &quot;3|13|5F|A6|A7|A8|A9|AA|AB|AC|AD|AE|AF|B0|B1|B2|B3|B4|B5|B6|B7&quot;
    S_BatchLines := A_BatchLines
    SetBatchLines, -1
    #IF Hotkey_IsRegControl()
    #IF Hotkey_Hook(&quot;K&quot;)
    #IF Hotkey_Hook(&quot;M&quot;)
    #IF Hotkey_Hook(&quot;L&quot;) &amp;&amp; Hotkey_HasKeyboardModifier()
    #IF Hotkey_Hook(&quot;R&quot;) &amp;&amp; Hotkey_HasKeyboardModifier()
    #IF Hotkey_Hook(&quot;J&quot;) &amp;&amp; !Hotkey_Main(&quot;GetMod&quot;)
    #IF Hotkey_Arr(&quot;Hook&quot;) &amp;&amp; !Hotkey_Hook(&quot;K&quot;)
    #IF
    Hotkey, IF, Hotkey_Hook(&quot;M&quot;)
    Loop, Parse, nmMouse, |
        Hotkey, % A_LoopField, Hotkey_View
    Hotkey, IF, Hotkey_Hook(&quot;K&quot;)
    Loop, Parse, nmMods, |
    {
        Hotkey, % A_LoopField, Hotkey_Mods
        Hotkey, % A_LoopField &quot; Up&quot;, Hotkey_ModsUp
    }
    Loop, Parse, scSymb, |
        Hotkey, % &quot;sc&quot; A_LoopField, Hotkey_ViewSC
    Loop, Parse, scOther, |
        Hotkey, % GetKeyName(&quot;sc&quot; A_LoopField), Hotkey_View
    Loop, Parse, vkNum, |
        Hotkey, % &quot;vk&quot; A_LoopField, Hotkey_ViewNum
    Loop, Parse, vkOther, |
        Hotkey, % GetKeyName(&quot;vk&quot; A_LoopField), Hotkey_View
	Hotkey, IF, Hotkey_Hook(&quot;L&quot;) &amp;&amp; Hotkey_HasKeyboardModifier()
    Hotkey, LButton, Hotkey_View
    Hotkey, IF, Hotkey_Hook(&quot;R&quot;) &amp;&amp; Hotkey_HasKeyboardModifier()
    Hotkey, RButton, Hotkey_View
    S_FormatInteger := A_FormatInteger
    SetFormat, IntegerFast, D
    Hotkey, IF, Hotkey_Hook(&quot;J&quot;) &amp;&amp; !Hotkey_Main(&quot;GetMod&quot;)
    Loop, 128
        Hotkey % Ceil(A_Index / 32) &quot;Joy&quot; Mod(A_Index - 1, 32) + 1, Hotkey_View
    SetFormat, IntegerFast, %S_FormatInteger%
    Hotkey, IF, Hotkey_IsRegControl()
    Hotkey, RButton, Hotkey_RButton
    Hotkey, RButton Up, Hotkey_Return
    Hotkey, IF, Hotkey_Arr(&quot;Hook&quot;) &amp;&amp; !Hotkey_Hook(&quot;K&quot;)
    Hotkey, AppsKey Up, Hotkey_Return
    Hotkey, +F10, Hotkey_Return
    Hotkey, IF
    SetBatchLines, %S_BatchLines%
    return

    Hotkey_RButton:
        Click
    Hotkey_Return:
        return
}

Hotkey_IsRegControl() {
    MouseGetPos,,,, Control, 2
    return Hotkey_ID(Control) != &quot;&quot;
}

Hotkey_IsMouseInput(Key) {
    static MouseInputs := &quot;|LButton|RButton|MButton|WheelDown|WheelUp|WheelRight|WheelLeft|XButton1|XButton2|&quot;
    return InStr(MouseInputs, &quot;|&quot; Key &quot;|&quot;)
}

Hotkey_HasKeyboardModifier() {
    return GetKeyState(&quot;Ctrl&quot;, &quot;P&quot;) || GetKeyState(&quot;Shift&quot;, &quot;P&quot;)
        || GetKeyState(&quot;Alt&quot;, &quot;P&quot;) || GetKeyState(&quot;LWin&quot;, &quot;P&quot;)
        || GetKeyState(&quot;RWin&quot;, &quot;P&quot;)
}

Hotkey_SyncPhysicalModifiers(K) {
    static Modifiers := { LCtrl: &quot;&lt;^&quot;, RCtrl: &quot;&gt;^&quot;, LAlt: &quot;&lt;!&quot;, RAlt: &quot;&gt;!&quot;
                        , LShift: &quot;&lt;+&quot;, RShift: &quot;&gt;+&quot;, LWin: &quot;&lt;#&quot;, RWin: &quot;&gt;#&quot; }
    for Name, Prefix in Modifiers {
        IsDown := GetKeyState(Name, &quot;P&quot;)
        K[&quot;M&quot; Name] := IsDown ? Name &quot;+&quot; : &quot;&quot;
        K[&quot;P&quot; Name] := IsDown ? Prefix : &quot;&quot;
    }
    K.MCtrl := K.MAlt := K.MShift := K.MWin := &quot;&quot;
    K.PCtrl := K.PAlt := K.PShift := K.PWin := &quot;&quot;
}

Hotkey_IsRegFocus() {
    ControlGetFocus, ControlNN, A
    ControlGet, hFocus, Hwnd, , %ControlNN%, A
    Hotkey_ID(hFocus) != &quot;&quot; ? Hotkey_Main(&quot;Control&quot;, hFocus) : 0
}

Hotkey_EventFocus(hWinEventHook, event, hwnd) {
    Hotkey_ID(hwnd) != &quot;&quot; ? Hotkey_Main(&quot;Control&quot;, hwnd) : Hotkey_Main(&quot;Control&quot;)
}

Hotkey_SetWinEventHook(eventMin, eventMax, hmodWinEventProc, lpfnWinEventProc, idProcess, idThread, dwFlags) {
    return DllCall(&quot;SetWinEventHook&quot; , &quot;UInt&quot;, eventMin, &quot;UInt&quot;, eventMax, &quot;Ptr&quot;, hmodWinEventProc
            , &quot;Ptr&quot;, lpfnWinEventProc, &quot;UInt&quot;, idProcess, &quot;UInt&quot;, idThread, &quot;UInt&quot;, dwFlags, &quot;Ptr&quot;)
}

; -------------------------------------- Save and get --------------------------------------

Hotkey_Arr(P*) {
    static Arr := {&quot;Empty&quot;:&quot;Нет&quot;}
    return P.MaxIndex() = 1 ? Arr[P[1]] : (Arr[P[1]] := P[2])
}

Hotkey_ID(P*) {
    static Arr := {}
    return P.MaxIndex() = 1 ? Arr[P[1]] : P.MaxIndex() = 2 ? (Arr[P[1]] := P[2]) : Arr.Delete(P[1])
}

Hotkey_Value(P*) {
    static Arr := {}
    return P.MaxIndex() = 1 ? Arr[P[1]] : P.MaxIndex() = 2 ? (Arr[P[1]] := P[2]) : Arr.Delete(P[1])
}

Hotkey_Options(P*) {
    static Arr := {}
    return P.MaxIndex() = 1 ? Arr[P[1]] : P.MaxIndex() = 2 ? (Arr[P[1]] := P[2]) : Arr.Delete(P[1])
}

Hotkey_ChangeOption(ID, Option) {
    (ID + 0 = &quot;&quot;) ? (Hwnd := Hotkey_ID(ID), Name := ID) : (Hwnd := ID, Name := Hotkey_ID(ID))
    if RegExMatch(Hotkey_Options(Hwnd), &quot;i)G(\d+)&quot;, g)
        Hotkey_Group(&quot;Delete&quot;, Name)
    if RegExMatch(Option, &quot;i)G(\d+)&quot;, g)
        Hotkey_Group(&quot;Set&quot;, Name, g1)
    return Hotkey_Options(Hwnd, Option)
}

Hotkey_Hook(Option) {
    return Hotkey_Arr(&quot;Hook&quot;) &amp;&amp; !!InStr(Hotkey_Arr(&quot;Hook&quot;), Option)
}

Hotkey_Delete(ID, Destroy = 0) {
    (ID + 0 = &quot;&quot;) ? (Hwnd := Hotkey_ID(ID), Name := ID) : (Hwnd := ID, Name := Hotkey_ID(ID))
    Hotkey_Group(&quot;Delete&quot;, Name)
    Hotkey_ID(Hwnd, &quot;&quot;, 1), Hotkey_ID(Name, &quot;&quot;, 1)
    Hotkey_Value(Hwnd, &quot;&quot;, 1), Hotkey_Value(Name, &quot;&quot;, 1)
    Hotkey_Options(Hwnd, &quot;&quot;, 1)
    ControlGetFocus, ControlNN, A
    ControlGet, hFocus, Hwnd, , %ControlNN%, A
    (hFocus = Hwnd ? Hotkey_Main(&quot;Control&quot;) : 0)
    if Destroy
        DllCall(&quot;DestroyWindow&quot;, &quot;Ptr&quot;, Hwnd)
    else
        PostMessage, 0x00CF, 0, , , % &quot;ahk_id&quot; Hwnd		;  EM_SETREADONLY
    return Hwnd
}

Hotkey_Set(Name, Value = &quot;&quot;) {
    Text := Hotkey_HKToStr(Value)
    SendMessage, 0xC, 0, &amp;Text, , % &quot;ahk_id&quot; Hotkey_ID(Name)
    return Text, Hotkey_Value(Name, Value), Hotkey_Value(Hotkey_ID(Name), Value)
}

Hotkey_Read(Name, Section = &quot;&quot;, FilePath = &quot;&quot;) {
    HK := Hotkey_IniRead(Name, Section, FilePath), Hotkey_Value(Name, HK)
    return Hotkey_HKToStr(HK)
}

Hotkey_IniPath(Path = &quot;&quot;) {
    return Path = &quot;&quot; ? Hotkey_Arr(&quot;IniPath&quot;) : Hotkey_Arr(&quot;IniPath&quot;, Path)
}

Hotkey_IniSection(Section = &quot;&quot;) {
    return Section = &quot;&quot; ? Hotkey_Arr(&quot;IniSection&quot;) : Hotkey_Arr(&quot;IniSection&quot;, Section)
}

Hotkey_IniRead(Name, Section = &quot;&quot;, FilePath = &quot;&quot;) {
    IniRead, Data, % FilePath = &quot;&quot; ? Hotkey_IniPath() : FilePath, % Section = &quot;&quot; ? Hotkey_IniSection() : Section, % Name, % A_Space
    return Data
}

Hotkey_IniWrite(ID, Section = &quot;&quot;, FilePath = &quot;&quot;) {
    Key := (ID + 0 = &quot;&quot;) ? ID : Hotkey_ID(ID)
    if (Key != &quot;&quot;)
        IniWrite, % Hotkey_Value(ID), % FilePath = &quot;&quot; ? Hotkey_IniPath() : FilePath, % Section = &quot;&quot; ? Hotkey_IniSection() : Section, % Key
}

; -------------------------------------- Group --------------------------------------

Hotkey_Group(Key = &quot;&quot;, p1 = &quot;&quot;, p2 = &quot;&quot;) {
    static NG := {}, GN := [], Blink := [], SaveCheck := [], i := 0
    if (Key = &quot;&quot;) {
        for k, Name in SaveCheck {
            if ((Value := Hotkey_Value(Name)) != &quot;&quot;) {
                (f := Hotkey_Arr(&quot;GroupEvents&quot;)) != &quot;&quot; &amp;&amp; (r := {}, r.names := [])
                for m, n in GN[NG[Name]] {
                    if (n != Name &amp;&amp; Hotkey_Equal(Value, Hotkey_Value(n))) {
                        Hotkey_Set(Name)
                        (f != &quot;&quot;) &amp;&amp; (r.names.Push(n), r.this := Name, r.value := Value, r.group := NG[Name])
                        if !DllCall(&quot;IsWindowVisible&quot;, &quot;Ptr&quot;, Hotkey_ID(n))
                            continue
                        DllCall(&quot;ShowWindowAsync&quot;, &quot;Ptr&quot;, Hotkey_ID(n), &quot;Int&quot;, 0)
                        Blink[Hotkey_ID(n)] := 1, i := 3
                        SetTimer, Hotkey_BlinkControl, -50
                    }
                }
            }
            SaveCheck.Delete(k)
        }
        (f != &quot;&quot;) &amp;&amp; (r.this != &quot;&quot;) &amp;&amp; %f%(r)
    }
    else if (Key = &quot;Set&quot;)
        NG[p1] := p2, IsObject(GN[p2]) ? GN[p2].Push(p1) : GN[p2] := [p1]
    else if (Key = &quot;SaveCheck&quot;)
        return 1, SaveCheck[p1] := Hotkey_ID(p1)
    else if (Key = &quot;Get&quot;)
        return NG[p1] != &quot;&quot;
    else if (Key = &quot;CheckAll&quot;) {
        for k, v in GN {
            for k, n in v {
                if ((Value := Hotkey_Value(n)) != &quot;&quot;) {
                    for k, m in v {
                        if (n != m &amp;&amp; Hotkey_Equal(Value, Hotkey_Value(m)))
                            Hotkey_Set(m)
					}
				}
			}
		}
    }
    else if (Key = &quot;Delete&quot;) {
        for k, v in GN[NG[p1]] {
            if (v = p1) {
                GN[NG[p1]].RemoveAt(k)
                break
            }
		}
        NG.Delete(p1)
    }
    return

    Hotkey_BlinkControl:
        for k in Blink
            DllCall(&quot;ShowWindowAsync&quot;, &quot;Ptr&quot;, k, &quot;Int&quot;, Mod(i, 2) ? 4 : 0)
        if (--i &gt; 0) || !(Blink := [])
            SetTimer, Hotkey_BlinkControl, -50
        return
}

Hotkey_Equal(HK1, HK2) {
    if (HK2 = &quot;&quot;)
        return 0
    if (HK1 = HK2)
        return 1
    if !(HK1 ~= &quot;S)[\^\+!#]&quot;) || !(HK2 ~= &quot;S)[\^\+!#]&quot;)
        return 0
    if (HK1 ~= &quot;S)[&lt;&gt;]&quot;) &amp;&amp; (HK2 ~= &quot;S)[&lt;&gt;]&quot;)
        return 0
    return (Hotkey_ModsSub(HK1) = Hotkey_ModsSub(HK2))
}

Hotkey_ModsSub(Value) {
    if !(Value ~= &quot;[&lt;&gt;]&quot;)
        return Value
    Value := StrReplace(Value, &quot;&lt;&quot;)
    Value := StrReplace(Value, &quot;&gt;&quot;)
    Value := StrReplace(Value, &quot;^^&quot;, &quot;^&quot;, , 1)
    Value := StrReplace(Value, &quot;!!&quot;, &quot;!&quot;, , 1)
    Value := StrReplace(Value, &quot;++&quot;, &quot;+&quot;, , 1)
    return StrReplace(Value, &quot;##&quot;, &quot;#&quot;, , 1)
}

; -------------------------------------- Format --------------------------------------

; http://forum.script-coding.com/viewtopic.php?pid=105023#p105023

Hotkey_HKToStr(HK) {
    static LRPrefix := [[&quot;&lt;^&quot;,&quot;LCtrl&quot;],[&quot;&gt;^&quot;,&quot;RCtrl&quot;],[&quot;&lt;!&quot;,&quot;LAlt&quot;],[&quot;&gt;!&quot;,&quot;RAlt&quot;]
                    ,[&quot;&lt;+&quot;,&quot;LShift&quot;],[&quot;&gt;+&quot;,&quot;RShift&quot;],[&quot;&lt;#&quot;,&quot;LWin&quot;],[&quot;&gt;#&quot;,&quot;RWin&quot;]]
    , Prefix := [[&quot;^&quot;,&quot;Ctrl&quot;],[&quot;!&quot;,&quot;Alt&quot;],[&quot;+&quot;,&quot;Shift&quot;],[&quot;#&quot;,&quot;Win&quot;]]
    , EngSym := {&quot;sc2&quot;:&quot;1&quot;,&quot;sc3&quot;:&quot;2&quot;,&quot;sc4&quot;:&quot;3&quot;,&quot;sc5&quot;:&quot;4&quot;,&quot;sc6&quot;:&quot;5&quot;,&quot;sc7&quot;:&quot;6&quot;
                ,&quot;sc8&quot;:&quot;7&quot;,&quot;sc9&quot;:&quot;8&quot;,&quot;scA&quot;:&quot;9&quot;,&quot;scB&quot;:&quot;0&quot;,&quot;scC&quot;:&quot;-&quot;,&quot;scD&quot;:&quot;=&quot;
                ,&quot;sc10&quot;:&quot;Q&quot;,&quot;sc11&quot;:&quot;W&quot;,&quot;sc12&quot;:&quot;E&quot;,&quot;sc13&quot;:&quot;R&quot;,&quot;sc14&quot;:&quot;T&quot;,&quot;sc15&quot;:&quot;Y&quot;
                ,&quot;sc16&quot;:&quot;U&quot;,&quot;sc17&quot;:&quot;I&quot;,&quot;sc18&quot;:&quot;O&quot;,&quot;sc19&quot;:&quot;P&quot;,&quot;sc1A&quot;:&quot;[&quot;,&quot;sc1B&quot;:&quot;]&quot;
                ,&quot;sc1E&quot;:&quot;A&quot;,&quot;sc1F&quot;:&quot;S&quot;,&quot;sc20&quot;:&quot;D&quot;,&quot;sc21&quot;:&quot;F&quot;,&quot;sc22&quot;:&quot;G&quot;,&quot;sc23&quot;:&quot;H&quot;
                ,&quot;sc24&quot;:&quot;J&quot;,&quot;sc25&quot;:&quot;K&quot;,&quot;sc26&quot;:&quot;L&quot;,&quot;sc27&quot;:&quot;`;&quot;,&quot;sc28&quot;:&quot;&#039;&quot;,&quot;sc29&quot;:&quot;``&quot;
                ,&quot;sc2B&quot;:&quot;\&quot;,&quot;sc2C&quot;:&quot;Z&quot;,&quot;sc2D&quot;:&quot;X&quot;,&quot;sc2E&quot;:&quot;C&quot;,&quot;sc2F&quot;:&quot;V&quot;,&quot;sc30&quot;:&quot;B&quot;
                ,&quot;sc31&quot;:&quot;N&quot;,&quot;sc32&quot;:&quot;M&quot;,&quot;sc33&quot;:&quot;,&quot;,&quot;sc34&quot;:&quot;.&quot;,&quot;sc35&quot;:&quot;/&quot;

                ,&quot;vk31&quot;:&quot;1&quot;,&quot;vk32&quot;:&quot;2&quot;,&quot;vk33&quot;:&quot;3&quot;,&quot;vk34&quot;:&quot;4&quot;,&quot;vk35&quot;:&quot;5&quot;,&quot;vk36&quot;:&quot;6&quot;
                ,&quot;vk37&quot;:&quot;7&quot;,&quot;vk38&quot;:&quot;8&quot;,&quot;vk39&quot;:&quot;9&quot;,&quot;vk30&quot;:&quot;0&quot;,&quot;vkBD&quot;:&quot;-&quot;,&quot;vkBB&quot;:&quot;=&quot;
                ,&quot;vk51&quot;:&quot;Q&quot;,&quot;vk57&quot;:&quot;W&quot;,&quot;vk45&quot;:&quot;E&quot;,&quot;vk52&quot;:&quot;R&quot;,&quot;vk54&quot;:&quot;T&quot;,&quot;vk59&quot;:&quot;Y&quot;
                ,&quot;vk55&quot;:&quot;U&quot;,&quot;vk49&quot;:&quot;I&quot;,&quot;vk4F&quot;:&quot;O&quot;,&quot;vk50&quot;:&quot;P&quot;,&quot;vkDB&quot;:&quot;[&quot;,&quot;vkDD&quot;:&quot;]&quot;
                ,&quot;vk41&quot;:&quot;A&quot;,&quot;vk53&quot;:&quot;S&quot;,&quot;vk44&quot;:&quot;D&quot;,&quot;vk46&quot;:&quot;F&quot;,&quot;vk47&quot;:&quot;G&quot;,&quot;vk48&quot;:&quot;H&quot;
                ,&quot;vk4A&quot;:&quot;J&quot;,&quot;vk4B&quot;:&quot;K&quot;,&quot;vk4C&quot;:&quot;L&quot;,&quot;vkBA&quot;:&quot;`;&quot;,&quot;vkDE&quot;:&quot;&#039;&quot;,&quot;vkC0&quot;:&quot;``&quot;
                ,&quot;vkDC&quot;:&quot;\&quot;,&quot;vk5A&quot;:&quot;Z&quot;,&quot;vk58&quot;:&quot;X&quot;,&quot;vk43&quot;:&quot;C&quot;,&quot;vk56&quot;:&quot;V&quot;,&quot;vk42&quot;:&quot;B&quot;
                ,&quot;vk4E&quot;:&quot;N&quot;,&quot;vk4D&quot;:&quot;M&quot;,&quot;vkBC&quot;:&quot;,&quot;,&quot;vkBE&quot;:&quot;.&quot;,&quot;vkBF&quot;:&quot;/&quot;}

    RegExMatch(HK, &quot;S)^\s*([~\*\$\^\+!#&lt;&gt;]*)\{?(.*?)}?\s*$&quot;, K)
    if (K2 = &quot;&quot;)
        return &quot;&quot; Hotkey_Arr(&quot;Empty&quot;)
    if (InStr(&quot;|&quot; K2, &quot;|sc&quot;) || InStr(&quot;|&quot; K2, &quot;|vk&quot;))
        K2 := Hotkey_Arr(&quot;OnlyEngSym&quot;) &amp;&amp; EngSym.HasKey(K2) ? EngSym[K2] : GetKeyName(K2)
    if (K1 != &quot;&quot;)
        for I, V in K1 ~= &quot;[&lt;&gt;]&quot; ? LRPrefix : Prefix
            K1 := StrReplace(K1, V[1], &quot;&quot;, R), R &amp;&amp; (M .= V[2] &quot;+&quot;)
    return M . (StrLen(K2) = 1 ? Format(&quot;{:U}&quot;, K2) : K2)
}

Hotkey_HKToSend(HK, Section = &quot;&quot;, FilePath = &quot;&quot;) {
    static LRPrefix := [[&quot;&lt;^&quot;,&quot;LCtrl&quot;],[&quot;&gt;^&quot;,&quot;RCtrl&quot;],[&quot;&lt;!&quot;,&quot;LAlt&quot;],[&quot;&gt;!&quot;,&quot;RAlt&quot;]
                       ,[&quot;&lt;+&quot;,&quot;LShift&quot;],[&quot;&gt;+&quot;,&quot;RShift&quot;],[&quot;&lt;#&quot;,&quot;LWin&quot;],[&quot;&gt;#&quot;,&quot;RWin&quot;]]
         , Prefix := [[&quot;^&quot;,&quot;LCtrl&quot;],[&quot;!&quot;,&quot;LAlt&quot;],[&quot;+&quot;,&quot;LShift&quot;],[&quot;#&quot;,&quot;LWin&quot;]]
    if (HK = &quot;&quot;)
        return
    if (Section != &quot;&quot;)
        IniRead, HK, % FilePath = &quot;&quot; ? Hotkey_IniPath() : FilePath, % Section, % HK, % A_Space
    RegExMatch(HK, &quot;S)^\s*([~\*\$\^\+!#&lt;&gt;]*)\{?(.*?)}?\s*$&quot;, K)
    if (K1 != &quot;&quot;) {
        for I, V in K1 ~= &quot;[&lt;&gt;]&quot; ? LRPrefix : Prefix {
            K1 := StrReplace(K1, V[1], &quot;&quot;, R)
            , R ? (M1 .= &quot;{&quot; V[2] &quot; Down}&quot;, M2 .= &quot;{&quot; V[2] &quot; Up}&quot;) : 0
		}
	}
    return M1 . &quot;{&quot; K2 &quot;}&quot; . M2
}

#if WinExist(&quot;ScreenShot ahk_class AutoHotkeyGUI&quot;)
#if

#if IsOverlaySelectionState(&quot;armed&quot;)
LButton:: BeginOverlaySelectionDrag()
RButton:: CancelOverlaySelection()
Esc:: CancelOverlaySelection()
#if

#if IsOverlaySelectionState(&quot;dragging&quot;)
*LButton Up:: FinishOverlaySelection()
RButton:: CancelOverlaySelection()
Esc:: CancelOverlaySelection()
#if

#if IsSelectionWindowActive()
^WheelUp::   NudgeSelectionEdge(&quot;Left&quot;  , -1)
^WheelDown:: NudgeSelectionEdge(&quot;Left&quot;  ,  1)
+WheelUp::   NudgeSelectionEdge(&quot;Top&quot;   , -1)
+WheelDown:: NudgeSelectionEdge(&quot;Top&quot;   ,  1)
!WheelUp::   NudgeSelectionEdge(&quot;Right&quot; ,  1)
!WheelDown:: NudgeSelectionEdge(&quot;Right&quot; , -1)
#WheelUp::   NudgeSelectionEdge(&quot;Bottom&quot;, -1)
#WheelDown:: NudgeSelectionEdge(&quot;Bottom&quot;,  1)
#if

#if WinActive(&quot;ScreenShot ahk_class AutoHotkeyGUI&quot;)
Esc:: PostMessage, Esc,,,, A
*WheelDown::
*WheelUp:: PostMessage, Wheel, A_ThisHotkey = &quot;*WheelUp&quot; ? 0 : 1,,, A
#if
</code></pre></div><p>Сохраните скрипт в кодировке UTF-8 с <strong>BOM</strong>, (иначе получите кракозябры вместо кириллицы).<br />Если кто-нибудь захочет задать вопрос по работе скрипта или сообщить о замеченных багах — прошу писать в <a href="http://forum.script-coding.com/viewtopic.php?id=3328">эту</a> тему.</p>]]></summary>
			<author>
				<name><![CDATA[teadrinker]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=24515</uri>
			</author>
			<updated>2026-07-04T23:47:07Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?id=18604&amp;action=new</id>
		</entry>
		<entry>
			<title type="html"><![CDATA[AHK v2: Альтернатива стандартному ToolTip]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?id=18479&amp;action=new" />
			<summary type="html"><![CDATA[<p>Конвертация <a href="https://forum.script-coding.com/viewtopic.php?id=18258">версии для AHK v1</a>.</p><p>Возможности альтернативного Tooltip:<br /></p><ul><li><p>Включение прозрачности, в том числе прозрачность для кликов мышью;</p></li><li><p>Автоматическое пропадание после указанного времени;</p></li><li><p>Пропадание при нажатии;</p></li><li><p>Изменение параметров шрифта;</p></li><li><p>Изменение цвета фона;</p></li><li><p>Уменьшенное мерцание при частом обновлении текста;</p></li><li><p>Работа с несколькими мониторами.</p></li></ul><p><a href="https://forum.script-coding.com/viewtopic.php?pid=162637#p162637">Тема для обсуждения.</a></p><div class="codebox"><pre><code>
class Tool_Tip
{
  ; #requires AutoHotkey v2.0.19
  ; Tool_Tip by Alectric
  ; https://forum.script-coding.com/
  ; Метод 	Show(Text:=&quot;&quot;,X:=&quot;&quot;,Y:=&quot;&quot;,Delay:=0,Trans:=&quot;&quot;,BackColor:=&quot;&quot;,Font:=&quot;&quot;,FontName:=&quot;&quot;,WW:=0,HH:=0)
  ; 	Text		- Текст который необходимо отобразить
  ; 	X, Y		- Координаты появления относительно экрана
  ; 	Delay		- задержка пропадания после вызова в миллисекундах
  ; 	Trans		- Прозрачность в формате AHK Gui (но от 2 до 255), false - не прозрачно, true - прозрачность=180; (также становится прозрачен для кликов мыши)
  ; 	BackColor	- Цвет фона
  ; 	Font		- Параметры шрифта в формате Gui (цвет, размер, наклон, толщина, подчеркнутый, зачеркнутый)
  ; 	FontName	- Название шрифта
  ; 	WW, HH		- Фиксированные ширина и высота (если оба значения не 0, то отрисовка в ~5 раз быстрее)
  ;
  ; Метод Hide()	- Скрывает Tooltip
  ;
  ; Свойство Pos	- Возвращает объект с параметрами окна {X:X,Y:Y,W:W,H:H}.

  static DPICoef:=Tool_Tip.GetDPICoef()

  __Delete()
  {
    ObjAddRef(ObjPtr(this))
    SetTimer this.obm,0
    this.TTW.Destroy()
    this.obm:=unset
;    tooltip &quot;Деструктор вызван&quot;
;    sleep 1000
;    tooltip
  }

  __New()
  {
    this.Visible:=false
    this.X:=0
    this.Y:=0
    this.W:=0
    this.H:=0
    this.BackColor:=&quot;0xFFFFFF&quot;
    this.Trans:=255
    this.Text:=&quot;&quot;
    this.Font:=&quot;s9&quot;
    this.FontName:=&quot;Arial&quot;
    this.TTW:=Gui(&quot;+ToolWindow -sysmenu -Resize +AlwaysOnTop -Caption +Border +E0x2080000&quot;)
    this.TTW.SetFont(this.Font,this.FontName)
    this.TTW.BackColor:=this.BackColor
    this.TTW.MarginX:=2
    this.TTW.MarginY:=2
    this.TTT:=this.TTW.Add(&quot;Text&quot;)
    this.obm:=ObjBindMethod(this,&quot;Hide&quot;)
    ObjRelease(ObjPtr(this))
    this.TTW.OnEvent(&quot;ContextMenu&quot;,this.obm)
    this.TTT.OnEvent(&quot;Click&quot;,this.obm)
  }

  Pos
  {
    get
    {
      this.TTW.GetPos(&amp;X,&amp;Y,&amp;W,&amp;H)
      return {X:X,Y:Y,W:W,H:H}
    }
  }

  Hide(*)
  {
    this.TTW.Hide()
    this.Visible:=false
  }

  Show(Text:=&quot;&quot;,X:=&quot;&quot;,Y:=&quot;&quot;,Delay:=0,Trans:=&quot;&quot;,BackColor:=&quot;&quot;,Font:=&quot;&quot;,FontName:=&quot;&quot;,WW:=0,HH:=0)
  {
    SaveCoordMode:=A_CoordModeMouse
    CoordMode &quot;Mouse&quot;,&quot;Screen&quot;
    MouseGetPos(&amp;mX,&amp;mY,&amp;mW,&amp;mC,2)
    CoordMode &quot;Mouse&quot;,SaveCoordMode
    if (X=&quot;&quot;)
      X:=mX+10
    if (Y=&quot;&quot;)
      Y:=mY+20
    if ((Font!=&quot;&quot; and Font!=this.Font) or (FontName!=&quot;&quot; and FontName!=this.FontName))
    {
      this.Font:=Font
      this.FontName:=FontName
      this.TTT.SetFont(this.Font,this.FontName)
    }
    W:=this.W
    H:=this.H
    if (Text!=&quot;&quot; and Text!=this.Text)
    {
      this.Text:=Text
      if (!WW or !HH)
      {
        ControlSetStyle(&quot;-1&quot;,this.TTT)
        Size:=this.Measure(Text)
      }
      else
      {
        ControlSetStyle(&quot;+1&quot;,this.TTT)
        Size:={X:X,Y:Y,W:WW,H:HH}
      }
      this.TTT.Move(,,Size.W,Size.H)
      this.TTT.Value:=Text
      W:=Size.W+8
      H:=Size.H+8
    }
    B:=this.Bound(X,Y,W,H)
    W:=Round(W/Tool_Tip.DPICoef)
    H:=Round(H/Tool_Tip.DPICoef)
    X:=B.X
    Y:=B.Y
    if (X!=this.X or Y!=this.Y or W!=this.W or H!=this.H or BackColor!=this.BackColor or !this.Visible)
    {
      this.X:=X
      this.Y:=Y
      this.W:=W
      this.H:=H
      this.Visible:=true
      if (BackColor!=&quot;&quot; and BackColor!=this.BackColor)
      {
        this.BackColor:=BackColor
        this.TTW.BackColor:=BackColor
      }
      this.TTW.show(&quot;x&quot; X &quot; y&quot; Y &quot; w&quot; W &quot; h&quot; H &quot; NoActivate&quot;)
    }
    if (Trans!=&quot;&quot; and Trans!=this.Trans)
    {
      this.Trans:=Trans
      if Trans
      {
        WinSetTransparent(Trans=1?180:Trans,this.TTW)
        WinSetExStyle(&quot;+0x20&quot;,this.TTW)
      }
      else
      {
        WinSetTransparent(255,this.TTW)
        WinSetExStyle(&quot;-0x20&quot;,this.TTW)
      }
    }
    SetTimer this.obm,-abs(Delay)
  }

  Measure(Text)
  {
    G:=Gui()
    G.SetFont(this.Font,this.FontName)
    G.MarginX:=2
    G.MarginY:=2
    T:=G.Add(&quot;Text&quot;,,Text)
    Controlgetpos(&amp;X,&amp;Y,&amp;W,&amp;H,T)
    G.Destroy()
    return {X:X,Y:Y,W:W,H:H}
  }

  Bound(X,Y,W:=0,H:=0)
  {
    this.last:=1
    Loop MonitorGetCount()
    {
      MonitorGet(A_Index,&amp;Left,&amp;Top,&amp;Right,&amp;Bottom)
      if (X&gt;=Left and X&lt;=Right and Y&gt;=Top and Y&lt;=Bottom)
      {
        MonitorGetWorkArea(A_Index,&amp;Left,&amp;Top,&amp;Right,&amp;Bottom)
        this.last:=a_index
        return {X:(X+W&gt;Right?Right-W:X)
               ,Y:(Y+H&gt;Bottom?Bottom-H:Y)
               ,B:a_index}
      }
    }
    MonitorGetWorkArea(this.last,&amp;Left,&amp;Top,&amp;Right,&amp;Bottom)
    return {X:(X+W&gt;Right?Right-W:X&lt;Left?Left:X)
           ,Y:(Y+H&gt;Bottom?Bottom-H:Y&lt;Top?Top:Y)
           ,B:this.last}
  }

  static GetDPICoef()
  {
    G:=Gui()
    T:=G.Add(&quot;Text&quot;,&quot;x0 y0 w1000 h1000&quot;)
    Controlgetpos(,,&amp;W,,T)
    G.Destroy()
    return W/1000
  }
}
</code></pre></div>]]></summary>
			<author>
				<name><![CDATA[Alectric]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=26027</uri>
			</author>
			<updated>2025-08-22T13:57:28Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?id=18479&amp;action=new</id>
		</entry>
		<entry>
			<title type="html"><![CDATA[AHK v2: Class Рисование графиков из массива данных]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?id=18426&amp;action=new" />
			<summary type="html"><![CDATA[<p>Конвертация <a href="https://forum.script-coding.com/viewtopic.php?id=17789">этой</a> версии.</p><p>Класс позволяет рассматривать собранные данные в графическом виде.<br />Поддерживает следующие типы массивов:<br /></p><ul><li><p>простой массив с данными; [ 1, 2, 3, ...]</p></li><li><p>массив данных с меткой времени в формате AHK (YYYYMMDDHH24MISS); [ [1,20230510200000], [2,20230510200001], ...]</p></li><li><p>массив с меткой времени с миллисекундами;&nbsp; [ [1,20230510200000,123], [2,20230510200001,432], ...]</p></li><li><p>массив массивов выше перечисленных типов.</p></li></ul><p>Возможности:<br /></p><ul><li><p>авто-масштаб шкалы;</p></li><li><p>общий/отдельный масштаб шкалы для нескольких графиков;</p></li><li><p>режим отображения по точкам/по времени;</p></li><li><p>пауза перемещения;</p></li><li><p>переход на точку/время;</p></li><li><p>перемещение графика левой кнопкой мыши;</p></li><li><p>выделение и увеличение выделенного правой кнопкой мыши;</p></li><li><p>возврат к предыдущим параметрам после увеличения кликом правой кн. мыши;</p></li><li><p>отображение значения при наведении мыши на точку;</p></li><li><p>сглаживание &quot;ступенек&quot; линий;</p></li><li><p>регулировка толщины линии;</p></li><li><p>изменения размера шрифта;</p></li><li><p>маркеры диапазона значений с расчетом времени, среднего значения, среднеквадратичного отклонения;</p></li><li><p>маркеры и настройки работают только для выбранного графика;</p></li><li><p>выбор цвета графика;</p></li><li><p>отделение графика в отдельное окно (если графиков несколько);</p></li><li><p>сохранение данных графика в файл;</p></li><li><p>загрузка данных графика из файла;</p></li><li><p>возможность добавить названия для каждого графика;</p></li><li><p>изменение отображаемого количества знаков после запятой;</p></li><li><p>изменение прозрачности линии графика;</p></li><li><p>читайте подсказки при наведении на элементы окна.</p></li></ul><p><a href="https://forum.script-coding.com/viewtopic.php?pid=162342#p162342">Тема для обсуждения.</a></p><p>Неоформленный пример (дооформлю позже):<br /></p><div class="codebox"><pre><code>
arr3:=[]
loop 7
  arr3.Push([])
c:=1
d:=0
e:=1
a:=0
t:=0
b:=0
arr:=[]

settimer ddd,150
settimer aaa,500

gra:=Graph()
gra.show()

gra.ArrName[1]:=&quot;кг&quot;
gra.ArrName[2]:=&quot;Двигатель&quot;
gra.ArrName[3]:=&quot;Температура&quot;
gra.ArrName[4]:=&quot;Разряжение&quot;
gra.ArrName[5]:=&quot;Натяжение&quot;
gra.ArrName[6]:=&quot;Угол&quot;
gra.ArrName[7]:=&quot;Ничего&quot;

gra.DataArray:=arr3

d:=e:=-1300

f1::
{
  gra.show()
}

f2::
{
  if (gra.DataArray=arr3)
    gra.DataArray:=arr
  else
    gra.DataArray:=arr3
}

ddd()
{
  global
  a+=0.1
  if (b&lt;-1 or b&gt;1)
    t:=!t
  if t
    b+=0.005
  else
    b-=0.005
  if (b&gt;1)
  {
    d:=random(-1000000000,1000000000)
    e:=random(-1000000000,1000000000)
    c:=random(0.5,1.5)
  }
  var:=random(50.5,100.5)
  shift:=0
  Val:=(sin(a)*var*b+shift)

  arr.Push(sin(a))

  arr3[1].Push(Val-30)
  arr3[4].Push([Val,A_Now,A_MSec])
  arr3[3].Push([-Val,A_Now,A_MSec])
  arr3[5].Push([cos(a*a)*0.1,A_Now,A_MSec])
  arr3[6].Push([cos(-a*2)*10*b+10,A_Now,A_MSec])
  arr3[7].Push([random(-1000000000,1000000000),A_Now,A_MSec])
}

aaa()
{
  static bool:=1
  bool:=!bool
  arr3[2].Push([bool,A_Now,A_MSec])

}

Esc::
{
global
gra:=&quot;&quot;
exitapp
}
</code></pre></div><p>Class:<br /></p><div class="codebox"><pre><code>class Graph
{
  ; #requires AutoHotkey v2.0.19
/*
Graph by Alectric
https://forum.script-coding.com/

Метод Call(Name:=&quot;&quot;,UpdatePeriod:=50)
	Создает экземпляр объекта.
	Name - имя будущего окна графика.
	UpdatePeriod - период обновления графика в окне, в миллисекундах.

Свойство DataArray - принимает массив с данными для отображения.
	Принимает следующие варианты массива:
		простой массив с данными; [ 1, 2, 3, ...]
		массив данных с меткой времени в формате AHK (YYYYMMDDHH24MISS); [ [1,20230510200000], [2,20230510200001], ...]
		массив с меткой времени с миллисекундами; [ [1,20230510200000,123], [2,20230510200001,432], ...]
		массив массивов выше перечисленных типов.

Свойство Pause - если true - останавливает прокрутку графика в реальном времени.
Метод MenuForward() - отключает паузу прокрутки и переносит отображение на самые свежие данные в массиве.
Метод MenuBackward() - включает паузу прокрутки и переносит отображение на самые старые данные в массиве.

Свойство Num - возвращает количество графиков в окне.
Свойство Select - задает/возвращает номер &quot;выбранного&quot; графика, если графиков больше чем один.
Свойство ArrName[n] - (строка) задает имя графику под номером n.
	Изначально данный массив содержит 10 элементов, на случай, если необходимо прописать имена раньше, чем привязать массив с данными. Используйте Push для расширения.

Метод MenuAutoScale() - переключает режим масштабирования &quot;выбранного&quot; графика между автоматическим и ручным.
Свойство AScale - задает режим масштабирования &quot;выбранного&quot; графика между автоматическим и ручным (true/false) {не работает до добавления массива с данными}.
Свойство AutoScale[n] - задает режим масштабирования графика n между автоматическим и ручным (true/false) {не работает до добавления массива с данными}.
Свойство ScaleY - задает масштаб шкалы &quot;выбранного&quot; графика, отключает авто-масштаб {не работает до добавления массива с данными}.
Свойство YScale[n] - задает масштаб шкалы графика n, авто-масштаб отключайте вручную {не работает до добавления массива с данными}.
Свойство ScaleX - задает масштаб по горизонтали: в миллисекундах (если отрисовка по времени) или в количестве точек (если отрисовка по точкам)
Свойство OScale - задает общий масштаб шкалы по вертикали (true/false).

Метод MenuScaleXByTime() - включает режим отрисовки графиков по времени.
Метод MenuScaleXByDots() - включает режим отрисовки графиков по точкам.

Метод MenuSmooth() - переключает режим сглаживания &quot;выбранного&quot; графика {не работает до добавления массива с данными}.
Свойство Smth - задает режим сглаживания &quot;выбранного&quot; графика (true/false) {не работает до добавления массива с данными}.
Свойство Smooth[n] - задает режим сглаживания графика n (true/false) {не работает до добавления массива с данными}.
Свойство Transparence - задает прозрачность &quot;выбранного&quot; графика (0-прозрачен, 255-видим) {не работает до добавления массива с данными}.
Свойство Colour - задает цвет без альфа-канала и оттенок фона текста &quot;выбранного&quot; графика {не работает до добавления массива с данными}.
Свойство Pen[n] - задает цвет с альфа-каналом графика n (0xaabbccdd, где: aa-прозрачность, bb-красный, cc-зеленый, dd-синий) {не работает до добавления массива с данными}.
Свойство Hue[n] - задает оттенок фона текста графика n {не работает до добавления массива с данными}.

Метод MenuAlwaysOnTop() - переключает режим &quot;всегда поверх окон&quot; для окна графика.
Свойство OnTop - задает режим &quot;всегда поверх окон&quot; для окна графика (true/false).

Метод MenuSave() - выводит диалог выбора файла чтобы сохранить график.
Метод MenuLoad() - выводит диалог выбора файла из которого загрузить файл графика.
Метод LoadFile(File) - загружает файл графика по пути &quot;File&quot; (!Путь не проверяется на корректность!).

Свойство Name - задает имя окна графика, после изменения этого свойства необходимо вызвать метод Show.
Свойство UpdatePeriod - задает период обновления окна графика, после изменения этого свойства необходимо вызвать метод Show.
Метод Show(x:=&quot;&quot;,y:=&quot;&quot;,w:=&quot;&quot;,h:=&quot;&quot;,NewName:=&quot;&quot;,NewUP:=&quot;&quot;)
	- отображает окно графика с необходимыми координатами (если заданы)
	- новым именем окна (если задано NewName)
	- и новым периодом обновления окна (если задан NewUP).
Метод Hide() - скрывает окно графика, и отключает отрисовку, чтобы снять нагрузку с процессора.

Метод MenuResetFont() - сбрасывает настройки размера шрифта отрисовки.
Метод SetFont(Font:=&quot;&quot;,Size:=&quot;&quot;,fStyle:=&quot;&quot;,NoWrap:=&quot;&quot;,RenderingHint:=&quot;&quot;,Vertical:=&quot;&quot;)
	Задает параметры шрифта отрисовки графика.
	Font - имя шрифта (Arial, Lucida Console, ...)
	Size - размер шрифта.
	fStyle - стиль шрифта (Regular|Bold|Italic|BoldItalic|Underline|Strikeout)
	NoWrap - режим переноса текста на следующую строку. (в контексте класса бесполезное свойство)
	RenderingHint - режим рендеринга текста.
		SystemDefault = 0
		SingleBitPerPixelGridFit = 1
		SingleBitPerPixel = 2
		AntiAliasGridFit = 3
		AntiAlias = 4
	Vertical - делает текст вертикальным (в контексте класса бесполезное свойство)

Сопутствующие функции (описаны в конце класса Graph):
	Горячие клавиши для изменения масштабирования:
		WheelUp
		WheelDown
	Функции выбора цвета:
		  Windows Color Picker Plus
		  https-//autohotkey.com/board/topic/91229-windows-color-picker-plus/
		ChooseColor(pRGB:=0,hOwner:=0,DlgX:=0,DlgY:=0)
		ColorWindowProc(hwnd, msg, wParam, lParam)
		BGR2RGB(Color)
	Функции загрузки/сохранения файла:
		ObjLoad(addr,&amp;offset:=0) ; load dumped object to variable; from memory (addr is int) or from file (addr is string)
		ObjDump(Path,obj) ; dump object to file
		RawObject(obj,ptr,offset:=0) ; dump object to memory
		RawObjectSize(Obj,sz:=0) ; get object&#039;s size in dumped condition
*/

  static hModule:=DllCall(&quot;LoadLibrary&quot;,&quot;str&quot;,&quot;gdiplus&quot;,&quot;Ptr&quot;)
  static pToken:=0, InstanceCount:=0
  static WinText:=&quot;Special text to identify the ownership of the Graph window.&quot;
  ;gdiplus\
  static hGdipCreateFont:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreateFont&quot;,&quot;Ptr&quot;)
  static hGdipCreateFontFamilyFromName:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreateFontFamilyFromName&quot;,&quot;Ptr&quot;)
  static hGdipCreateFromHDC:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreateFromHDC&quot;,&quot;Ptr&quot;)
  static hGdipCreatePen1:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreatePen1&quot;,&quot;Ptr&quot;)
  static hGdipCreateSolidFill:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreateSolidFill&quot;,&quot;Ptr&quot;)
  static hGdipCreateStringFormat:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreateStringFormat&quot;,&quot;Ptr&quot;)
  static hGdipDeleteBrush:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDeleteBrush&quot;,&quot;Ptr&quot;)
  static hGdipDeleteFont:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDeleteFont&quot;,&quot;Ptr&quot;)
  static hGdipDeleteFontFamily:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDeleteFontFamily&quot;,&quot;Ptr&quot;)
  static hGdipDeletePen:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDeletePen&quot;,&quot;Ptr&quot;)
  static hGdipDeleteStringFormat:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDeleteStringFormat&quot;,&quot;Ptr&quot;)
  static hGdipDrawEllipse:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDrawEllipse&quot;,&quot;Ptr&quot;)
  static hGdipDrawLine:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDrawLine&quot;,&quot;Ptr&quot;)
  static hGdipDrawLines:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDrawLines&quot;,&quot;Ptr&quot;)
  static hGdipDrawString:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDrawString&quot;,&quot;Ptr&quot;)
  static hGdipFillRectangle:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipFillRectangle&quot;,&quot;Ptr&quot;)
  static hGdiplusShutdown:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdiplusShutdown&quot;,&quot;Ptr&quot;)
  static hGdiplusStartup:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdiplusStartup&quot;,&quot;Ptr&quot;)
  static hGdipMeasureString:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipMeasureString&quot;,&quot;Ptr&quot;)
  static hGdipSetSmoothingMode:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipSetSmoothingMode&quot;,&quot;Ptr&quot;)
  static hGdipSetStringFormatAlign:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipSetStringFormatAlign&quot;,&quot;Ptr&quot;)
  static hGdipSetTextRenderingHint:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipSetTextRenderingHint&quot;,&quot;Ptr&quot;)
  static hGdipReleaseDC:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipReleaseDC&quot;,&quot;Ptr&quot;)
  static hGdipDeleteGraphics:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDeleteGraphics&quot;,&quot;Ptr&quot;)
  static hGdipCreateBitmapFromHBITMAP:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreateBitmapFromHBITMAP&quot;,&quot;Ptr&quot;)
  static hGdipCreateTexture2:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreateTexture2&quot;,&quot;Ptr&quot;)
  static hGdipDisposeImage:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDisposeImage&quot;,&quot;Ptr&quot;)
  static hGdipDrawRectangle:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDrawRectangle&quot;,&quot;Ptr&quot;)

  static min:=-9223372036854775808
  static max:= 9223372036854775807
  static MaxTime:=30*24*3600000 ; миллисекунд
  static WheelUp:=false
  static WheelDown:=false
  static HotKeymW:=0

  __Delete()
  {
    if !this.Exist
      return
    this.Exist:=false
    loop this.RefCount
      ObjAddRef(ObjPtr(this))
;    tooltip &quot;Деструктор вызван.`n&quot; Graph.InstanceCount &quot;`n&quot; this.Name
;    sleep 1000
;    tooltip
    settimer this.UpdateTimer,0
    this.UpdateTimer:=&quot;&quot;
    DllCall(Graph.hGdipDeleteGraphics,&quot;UPtr&quot;,this.pGraphics)
    DllCall(&quot;SelectObject&quot;,&quot;Ptr&quot;,this.hDC,&quot;Ptr&quot;,this.obm)
    DllCall(&quot;DeleteObject&quot;,&quot;UPtr&quot;,this.hBitmap)
    DllCall(&quot;ReleaseDC&quot;,&quot;Ptr&quot;,this.GuiGDIP.Hwnd,&quot;Ptr&quot;,this.hDC)
    this.GuiHelp.Destroy()
    this.GuiGoToT.Destroy()
    this.GuiGDIP.Destroy()
    this.GuiGraph.Destroy()
    this.GraphMenu.Delete()
    this.GraphMenuSettings.Delete()
    loop this.Child.Length
    {
      ObjAddRef(ObjPtr(this))
      this.Child[a_index]:=&quot;&quot;
    }
    Graph.InstanceCount--
    if (Graph.InstanceCount=0)
    {
      DllCall(Graph.hGdiplusShutdown,&quot;Ptr&quot;,Graph.pToken)
      Graph.pToken:=&quot;&quot;
    }
  }

  mi:={MenuAlwaysOnTop:&quot;Всегда поверх окон&quot;
  ,MenuAutoScale:&quot;Автомасштаб&quot;
  ,MenuSmooth:&quot;Сглаживать ступеньки&quot;
  ,MenuFlFt:&quot;Количество знаков после запятой...&quot;
  ,MenuUpdPrd:&quot;Период обновления графика...&quot;
  ,MenuScaleXByTime:&quot;Масштабировать X по времени&quot;
  ,MenuScaleXByDots:&quot;Масштабировать X по точкам&quot;
  ,MenuResetFont:&quot;Сбросить размер шрифта&quot;
  ,MenuGColor:&quot;Выбрать цвет графика...&quot;
  ,MenuName:&quot;Изнменить имя выбранного графика...&quot;
  ,MenuSave:&quot;Сохранить график в файл...&quot;
  ,MenuLoad:&quot;Загрузить график из файла...&quot;
  ,MenuClose:&quot;Х &quot;
  ,MenuPause:&quot;(||)&quot;
  ,MenuForward:&quot;(&gt;|)&quot;
  ,MenuBackward:&quot;(|&lt;)&quot;
  ,MenuGoTo:&quot;Перейти&quot;
  ,MenuMarkers:&quot;Маркеры&quot;
  ,MenuHelp:&quot;Справка&quot;
  ,Settings:&quot;Настр.&quot;}

  __New(Name:=&quot;&quot;,UpdatePeriod:=50)
  {
    ; Запустить библиотеку гдиплюс
    if !Graph.pToken
    {
      si:=Buffer(A_PtrSize=8 ? 24 : 16,0)
      NumPut(&quot;UChar&quot;,1,si,0)
      DllCall(Graph.hGdiplusStartup,&quot;UPtr*&quot;,&amp;pToken:=0,&quot;Ptr&quot;,si.Ptr,&quot;Ptr&quot;,0)
      Graph.pToken:=pToken
    }
    Graph.InstanceCount++

    this.Name:=Name
    if !Name
      this.Name:=&quot;График &quot; Graph.InstanceCount

    ; Инициализация переменных

    this.ArrName:=[]
    loop 10
      this.ArrName.Push(&quot;&quot;)
    this.IsChild:=false
    this.Child:=[]
    this.Parent:=0
    this.Exist:=true
    this.SaveArrName:=[]
    this.SetParentOnce:=false
    this.CreateTime:=[A_Now,A_msec]
    this.Data:=[]
    this.NewData:=&quot;&quot;
    this.Loaded:=0
    this.FloatFormat:=3
    this.Drawing:=false
    this.UpdatePeriod:=UpdatePeriod
    this.hFormat:=0
    this.hFamily:=0
    this.hFont:=0
    this.prevRenderingHint:=&quot;&quot;
    this.prevBrushColor:=0
    this.prevPenColor:=0
    this.prevNoWrap:=0
    this.prevVertical:=0
    this.prevFont:=&quot;&quot;
    this.prevSize:=0
    this.prevfStyle:=&quot;&quot;
    this.prevColour:=0
    this.pBrush:=0
    this.pPen:=0
    this.FontpBrush:=0
    this.GPosW:=0
    this.GPosH:=0
    this.GPosH_:=0
    this.Pause:=false
    this.OScale:=true

    this.GetmCoordOnce:=false
    this.GetStartPosRectOnce:=false

    this.ZoomX:=100
    this.ZoomT:=30000
    this.OldZoomX:=this.ZoomX
    this.OldZoomT:=this.ZoomT
    this.SaveZoomX:=this.ZoomX
    this.SaveZoomT:=this.ZoomT
    this.ShowZoomTip:=0
    this.XTOffset:=0
    this.X_Offset:=0
    this.ZeroPos:=30
    this.ZeroPosU:=1
    this.GPosH:=160

    this.AStartTime:=0
    this.StampOffset:=0
    this.MarkerOffset:=0
    this.StampsNum:=5
    this.XMarker:=[-9999,-9999]
    this.MenuOffset:=0
    this.Old_XTOffset:=0
    this.OldXTOffset:=0

    this.Sel:=1
    this.Proc:=0
    this.StartTime:=0
    this.OldmX:=0
    this.OldmY:=0
    this.FontpBrush:=0
    this.SaveStrt:=[0,0]
    this.ShowMarkers:=false
    this.OffsetSetting:=false
    this.ShiftStrt:=false
    this.MouseMoved:=false
    this.OldX_Offset:=0
    this.StampPos:={X:0,Y:0,W:0,H:this.GPosH,Chars:0,Lines:0}
    this.ETPos:={X:0,Y:0,W:0,H:0,Chars:0,Lines:0}
    this.TipPos:={X:0,Y:0,W:0,H:0,Chars:0,Lines:0}
    this.iZoomPos:={X:0,Y:0,W:0,H:0,Chars:0,Lines:0}
    this.OScalePos:={X:0,Y:0,W:0,H:0,Chars:0,Lines:0}
    this.Setting:=false
    this.RectZoom:=false
    this.XMarkerOnce:=false
    this.AlwaysOnTop:=false
    this.PrevW:=0
    this.PrevH:=0
    this.TimeOut:=false
    this.TOZoomX:=0
    this.TOZoomT:=0

    ; Создать графику
    this.Create()
    this.ReInit()

    ; Настройки по умолчанию
    this.Font:=&quot;Lucida Console&quot; ; шрифт
    this.FontSize:=12 ; размер шрифта
    this.SetFont(this.Font,this.FontSize)
    this.Font_Colour:=0xff000000 ; цвет шрифта
    this.Font_Back:=0xd0ffffff ; цвет фона шрифта
    this.WhiteBrush:=0xffffffff ; для общего фона
    this.GrayPen:=0xaa808080 ; для оси
    this.GrayPen2:=0xaae0e0e0 ; для оси
    this.RedPen:=0xffff0000 ; для маркера
    this.LightGreenBrush:=0xffd5ffd5 ; для фона информации
    this.LightBlueBrush:=0xffd5d5ff ; для фона Меню
    this.BluePen:=0xff0000ff ; для графика

    s:=this.mi.MenuClose this.mi.MenuPause this.mi.MenuForward this.mi.MenuBackward this.mi.MenuGoTo this.mi.MenuMarkers this.mi.Settings this.mi.MenuHelp
    loop 12
      s.=&quot;_&quot;
    w:=this.MeasureString(s,this.CreateRectF(0,0,1400,100))
    ; смещение исходной высоты если нет данных

    this.MessagePos:={X:0,Y:0,W:0,H:w.H,Chars:0,Lines:0}

; Добавление меню
    ; Меню настроек
    this.GraphMenuSettings:=Menu()

    obm:=ObjBindMethod(this,&quot;MenuSave&quot;)
    this.GraphMenuSettings.add(this.mi.MenuSave,obm)

    obm:=ObjBindMethod(this,&quot;MenuLoad&quot;)
    this.GraphMenuSettings.add(this.mi.MenuLoad,obm)

    this.GraphMenuSettings.add()

    obm:=ObjBindMethod(this,&quot;MenuAlwaysOnTop&quot;)
    this.GraphMenuSettings.add(this.mi.MenuAlwaysOnTop,obm)

    obm:=ObjBindMethod(this,&quot;MenuAutoScale&quot;)
    this.GraphMenuSettings.add(this.mi.MenuAutoScale,obm)
    this.GraphMenuSettings.check(this.mi.MenuAutoScale)

    obm:=ObjBindMethod(this,&quot;MenuSmooth&quot;)
    this.GraphMenuSettings.add(this.mi.MenuSmooth,obm)
    this.GraphMenuSettings.check(this.mi.MenuSmooth)

    obm:=ObjBindMethod(this,&quot;MenuFlFt&quot;)
    this.GraphMenuSettings.add(this.mi.MenuFlFt,obm)

    obm:=ObjBindMethod(this,&quot;MenuUpdPrd&quot;)
    this.GraphMenuSettings.add(this.mi.MenuUpdPrd,obm)

    this.GraphMenuSettings.add()

    obm:=ObjBindMethod(this,&quot;MenuScaleXByTime&quot;)
    this.GraphMenuSettings.add(this.mi.MenuScaleXByTime,obm,&quot;+Radio&quot;)
    this.GraphMenuSettings.check(this.mi.MenuScaleXByTime)
    this.ScaleXByTime:=true

    obm:=ObjBindMethod(this,&quot;MenuScaleXByDots&quot;)
    this.GraphMenuSettings.add(this.mi.MenuScaleXByDots,obm,&quot;+Radio&quot;)

    this.GraphMenuSettings.add()

    obm:=ObjBindMethod(this,&quot;MenuName&quot;)
    this.GraphMenuSettings.add(this.mi.MenuName,obm)

    obm:=ObjBindMethod(this,&quot;MenuGColor&quot;)
    this.GraphMenuSettings.add(this.mi.MenuGColor,obm)

    obm:=ObjBindMethod(this,&quot;MenuResetFont&quot;)
    this.GraphMenuSettings.add(this.mi.MenuResetFont,obm)

    ; основное меню
    this.GraphMenu:=MenuBar()
;    obm:=ObjBindMethod(this,&quot;MenuClose&quot;)
;    this.GraphMenu.add(this.mi.MenuClose,obm)

    this.GraphMenu.add(this.mi.Settings,this.GraphMenuSettings)

    obm:=ObjBindMethod(this,&quot;MenuBackward&quot;)
    this.GraphMenu.add(this.mi.MenuBackward,obm)

    obm:=ObjBindMethod(this,&quot;MenuPause&quot;)
    this.GraphMenu.add(this.mi.MenuPause,obm)

    obm:=ObjBindMethod(this,&quot;MenuForward&quot;)
    this.GraphMenu.add(this.mi.MenuForward,obm)

    obm:=ObjBindMethod(this,&quot;MenuGoTo&quot;)
    this.GraphMenu.add(this.mi.MenuGoTo,obm)

    obm:=ObjBindMethod(this,&quot;MenuMarkers&quot;)
    this.GraphMenu.add(this.mi.MenuMarkers,obm)

    obm:=ObjBindMethod(this,&quot;MenuHelp&quot;)
    this.GraphMenu.add(this.mi.MenuHelp,obm)

; Создание основного окна
    this.GuiGraph:=Gui(&quot;+MinSize400x100 +Resize&quot;,this.Name)
    this.GuiGraph.MarginX:=0
    this.GuiGraph.MarginY:=0
    this.GuiGraph.MenuBar:=this.GraphMenu
    this.GuiGraph.Add(&quot;Text&quot;,&quot;w0 h0&quot;,Graph.WinText)
    obm:=ObjBindMethod(this,&quot;DropFiles&quot;)
    this.GuiGraph.OnEvent(&quot;DropFiles&quot;,obm)
    obm:=ObjBindMethod(this,&quot;Hide&quot;)
    this.GuiGraph.OnEvent(&quot;Close&quot;,obm)

; Создание окна отрисовки графики
    this.GuiGDIP:=Gui(&quot;+owner&quot; this.GuiGraph.Hwnd &quot; -Caption +ToolWindow +E0x08080020&quot;) ; WS_EX_LAYERED := 0x80000
    this.GuiGDIP.Show(&quot;NoActivate x0 y0 w1 h1&quot;)

; Создание окна перехода по времени
    this.GuiGoToT:=Gui(&quot;+owner&quot; this.GuiGraph.Hwnd &quot; -sysmenu -Resize&quot;,&quot;Перейти на время&quot;)
    this.GuiGoToT.SetFont(&quot;s10&quot;)
    this.hTT:=this.GuiGoToT.Add(&quot;DateTime&quot;,&quot;xm+1&quot;,&quot;HH:mm:ss        dd.MM.yyyy&quot;)
    obm:=ObjBindMethod(this,&quot;TTOk&quot;)
    this.GuiGoToT.Add(&quot;Button&quot;,&quot;xm+5 y+5&quot;,&quot;Ok&quot;).OnEvent(&quot;Click&quot;,obm)
    obm:=ObjBindMethod(this,&quot;TTCancel&quot;)
    this.GuiGoToT.Add(&quot;Button&quot;,&quot;x+10&quot;,&quot;Отмена&quot;).OnEvent(&quot;Click&quot;,obm)

; Создание окна перехода к точке
    this.GuiGoToX:=Gui(&quot;+owner&quot; this.GuiGraph.Hwnd &quot; -sysmenu -Resize&quot;,&quot;Перейти к точке&quot;)
    this.GuiGoToX.SetFont(&quot;s10&quot;)
    this.hTX:=this.GuiGoToX.Add(&quot;Edit&quot;,&quot;xm+1 w150 Number&quot;,1)
    obm:=ObjBindMethod(this,&quot;TXOk&quot;)
    this.GuiGoToX.Add(&quot;Button&quot;,&quot;xm+5 y+5&quot;,&quot;Ok&quot;).OnEvent(&quot;Click&quot;,obm)
    obm:=ObjBindMethod(this,&quot;TXCancel&quot;)
    this.GuiGoToX.Add(&quot;Button&quot;,&quot;x+10&quot;,&quot;Отмена&quot;).OnEvent(&quot;Click&quot;,obm)

; Создание окна справки
    this.GuiHelp:=Gui(&quot;+owner&quot; this.GuiGraph.Hwnd &quot; -sysmenu -Resize&quot;,&quot;Справка&quot;)
    this.GuiHelp.SetFont(&quot;s10&quot;)
    HelpText:=
    (
      &quot;Левая кнопка мыши - перемещение графика.
      Правая кнопка мыши - выделение и увеличение выделенного участка.
      Клик правой кнопкой - возврат к предыдущему масштабу после увеличения.&quot;
    )
    this.GuiHelp.Add(&quot;Text&quot;,,HelpText)
    obm:=ObjBindMethod(this,&quot;HelpClose&quot;)
    this.GuiHelp.Add(&quot;Button&quot;,&quot;xm+10 y+5&quot;,&quot;Закрыть&quot;).OnEvent(&quot;Click&quot;,obm)

; Создание окна изменения имени выбранного графика
    this.GuiName:=Gui(&quot;+owner&quot; this.GuiGraph.Hwnd &quot; -sysmenu -Resize&quot;,&quot;Имя графика&quot;)
    this.GuiName.SetFont(&quot;s10&quot;)
    this.GuiNameVal:=this.GuiName.Add(&quot;Edit&quot;,&quot;xm+1 w150&quot;,3)
    obm:=ObjBindMethod(this,&quot;NameOk&quot;)
    this.GuiName.Add(&quot;Button&quot;,&quot;xm+5 y+5&quot;,&quot;Ok&quot;).OnEvent(&quot;Click&quot;,obm)
    obm:=ObjBindMethod(this,&quot;NameCancel&quot;)
    this.GuiName.Add(&quot;Button&quot;,&quot;x+10&quot;,&quot;Отмена&quot;).OnEvent(&quot;Click&quot;,obm)

; Создание окна изменения значения настройки
    this.GuiVal:=Gui(&quot;+owner&quot; this.GuiGraph.Hwnd &quot; -sysmenu -Resize&quot;,&quot;Изменить&quot;)
    this.GuiVal.SetFont(&quot;s10&quot;)
    this.GuiValText1:=this.GuiVal.Add(&quot;Text&quot;,&quot;xm+10 w350&quot;)
    this.GuiValVal:=this.GuiVal.Add(&quot;Edit&quot;,&quot;xm+5 y+5 wp Number&quot;,3)
    this.GuiValText2:=this.GuiVal.Add(&quot;Text&quot;,&quot;xm+10 y+1 wp&quot;)
    obm:=ObjBindMethod(this,&quot;ValAcc&quot;)
    this.GuiVal.Add(&quot;Button&quot;,&quot;xm+5 y+5&quot;,&quot;Применить&quot;).OnEvent(&quot;Click&quot;,obm)
    obm:=ObjBindMethod(this,&quot;ValOk&quot;)
    this.GuiVal.Add(&quot;Button&quot;,&quot;x+10&quot;,&quot;Ok&quot;).OnEvent(&quot;Click&quot;,obm)
    obm:=ObjBindMethod(this,&quot;ValCancel&quot;)
    this.GuiVal.Add(&quot;Button&quot;,&quot;x+10&quot;,&quot;Отмена&quot;).OnEvent(&quot;Click&quot;,obm)
    this.Whichval:=&quot;&quot;

; Запомнить метку для таймера
    this.UpdateTimer:=ObjBindMethod(this,&quot;UpdateGraph&quot;)
    this.GuiHiden:=true
;    this.UpdateGraph()

    this.RefCount:=30
    loop this.RefCount
      ObjRelease(ObjPtr(this))

    /*
    форматы обрабатывемых данных

    Data:=[1,2,3,4,5,6,76,56,3,45,6,22,45,234]
    - простой массив значений

    Data:=[[1,A_Now],[2,A_Now],[3,A_Now],[4,A_Now]]
    - массив с меткой времени [[значение,время],[значение,время]...]

    Data:=[[1,A_Now,A_MSec],[2,A_Now,A_MSec],[3,A_Now,A_MSec],[4,A_Now,A_MSec]]
    - массив с меткой времени с миллисекундами [[значение,время,миллисекунды],[значение,время,миллисекунды]...]

    Data:=[[1,2],[[1,A_Now],[2,A_Now]],[[1,A_Now,A_MSec],[2,A_Now,A_MSec]]]
    - массив любых выше перечисленных массивов
    */
  }

  DataArray
  {
    set
    {
      if this.SaveArrName.Length
      {
        this.ArrName:=this.SaveArrName.Clone()
        this.SaveArrName:=[]
      }
      this.Loaded:=2
      this.NewData:=Value
    }
    get
    {
      return this.Data
    }
  }

  Num
  {
    get
    {
      return this.Smooth.Length
    }
  }

  Select
  {
    set
    {
      if !IsInteger(Value)
        return
      if (Value&gt;0 and Value&lt;this.Smooth.Length)
      {
        this.Sel:=Value
      }
    }
    get
    {
      return this.Sel
    }
  }

  AScale
  {
    set
    {
      this.AutoScale[this.Sel]:=Value
    }
    get
    {
      return this.AutoScale[this.Sel]
    }
  }

  ScaleY
  {
    set
    {
      if !IsNumber(Value)
        return
      this.AutoScale[this.Sel]:=false
      this.YScale[this.Sel]:=Value
    }
    get
    {
      return this.YScale[this.Sel]
    }
  }

  ScaleX
  {
    set
    {
      if (!IsNumber(Value) or Value&lt;1)
        return
      if this.ScaleXByTime
        this.ZoomT:=Value
      else
        this.ZoomX:=Value
    }
    get
    {
      if this.ScaleXByTime
        return this.ZoomT
      else
        return this.ZoomX
    }
  }

  Smth
  {
    set
    {
      this.Smooth[this.Sel]:=Value
    }
    get
    {
      return this.Smooth[this.Sel]
    }
  }

  OnTop
  {
    set
    {
      if (Value and !this.AlwaysOnTop)
        this.MenuAlwaysOnTop()
      if (!Value and this.AlwaysOnTop)
        this.MenuAlwaysOnTop()
    }
    get
    {
      return this.AlwaysOnTop
    }
  }

  Transparence
  {
    set
    {
      if !IsInteger(Value)
        return
      this.Transp[this.Sel]:=Value
      this.Pen[this.Sel]:=this.Transp[this.Sel]&lt;&lt;24|this.Pen[this.Sel]&amp;0xffffff
    }
    get
    {
      return this.Transp[this.Sel]
    }
  }

  Colour
  {
    set
    {
      if !IsInteger(Value)
        return
      tmp2:=this.RGBToHSV(Value)
      if ((tmp2&gt;&gt;8)&amp;0xff=0)
      {
        tmp3:=(tmp2&amp;0xff)
        if (tmp3&lt;127)
          tmp3:=127
        this.Hue[this.Sel]:=tmp3&lt;&lt;8
      }
      else
        this.Hue[this.Sel]:=tmp2&gt;&gt;16
      this.Pen[this.Sel]:=0xff000000 | Value
    }
    get
    {
      return this.Pen[this.Sel]
    }
  }

  HitBox(X,Y,Rect)
  {
    return (X&gt;Rect.X and X&lt;Rect.X+Rect.W and Y&gt;Rect.Y and Y&lt;Rect.Y+Rect.H)
  }

;/////////////////////////////////////////////////////////////////////////////////////////////////
;|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|
;||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////||
;|||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||
;||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/||||
  ReInit()
  {
    this.prevArrSize:=[]
    this.SearchStop:=[]
    this.SearchSkip:=[]
    this.LineW:=[]
    this.Transp:=[]
    this.Smooth:=[]
    this.AutoScale:=[]
    this.SaveAutoScale:=[]
    this.SaveYScale:=[]
    this.GHide:=[]
    this.YScale:=[]
    this.Pen:=[]
    this.Hue:=[]
    this.ArrSize:=[]
    this.prev_ArrSize:=[]
    this.StampT:=[]
    this.LockYP:=[]
    this.LockYM:=[]
    this.Lock_hi_lim:=[]
    this.Lock_lo_lim:=[]
    this.Val_UnderGraph_Drawed:=[]
    this.UnderGX:=[]
    this.UnderGY:=[]
    this.UnderGV:=[]
    this.Sel:=1
    this.ZeroPos:=this.GPosH//3
    this.TimeStampExist:=[]
  }
  GetTime()
  {
    t1:=A_Now
    t2:=A_MSec
    while (t2&lt;5)
    {
      t1:=A_Now
      t2:=A_MSec
    }
    return [t1,t2]
  }
  UpdateGraph()
  {
    ListLines 0
    ObjAddRef(ObjPtr(this))
    if this.GuiHiden
    {
      ObjRelease(ObjPtr(this))
      return
    }
    this.Drawing:=true
    if (this.Loaded and !this.NewData)
    {
      this.MenuForward()
      if (this.Loaded=1)
        this.Pause:=true
      this.Loaded:=0
    }
    if this.NewData
    {
      this.ReInit()
      this.Data:=this.NewData
      this.NewData:=&quot;&quot;
    }

    DllCall(&quot;QueryPerformanceFrequency&quot;, &quot;Int64*&quot;,&amp;frequency:=0)
    DllCall(&quot;QueryPerformanceCounter&quot;,&quot;Int64*&quot;,&amp;EndTime:=0)
    CycleTime:=round((EndTime-this.StartTime)/frequency*1000,1)
    DllCall(&quot;QueryPerformanceCounter&quot;, &quot;Int64*&quot;,&amp;StartTime:=0)
    this.StartTime:=StartTime

; Взять координаты мыши
    SaveCoordMode:=A_CoordModeMouse
    CoordMode &quot;Mouse&quot;,&quot;Client&quot;
    MouseGetPos(&amp;mX,&amp;mY,&amp;mW,&amp;mC,2)
    CoordMode &quot;Mouse&quot;,SaveCoordMode
    MouseIn:=(this.GuiGraph.hwnd=mW and mY&gt;0)
    DeltaX:=this.OldmX-mX
    DeltaY:=this.OldmY-mY

    this.Pos:=this.DrawString(0,0,0,,,,this.Font_Colour,this.Font_Back)
; очистить поле
    this.FillRectangle(this.WhiteBrush,0,0,this.GPosW,this.GPosH_)

; Добавление координаты маркеру
    if (MouseIn and getkeystate(&quot;RButton&quot;,&quot;P&quot;) and mY&lt;this.GPosH and !this.XMarkerOnce)
    {
      this.XMarkerOnce:=true
      this.XMarker[2]:=mX
    }
    else if (this.XMarkerOnce and !getkeystate(&quot;RButton&quot;,&quot;P&quot;))
      this.XMarkerOnce:=false
; Пауза скролинга для режима отображения по времени
    if this.Pause
    {
      this.XTOffset+=CycleTime
      this.OldXTOffset+=CycleTime
    }

; Определение массива массивов
    ArAr:=false
    if (this.Data.Length and isObject(this.Data[1])) ; and this.Data[1].Length)
    {
      if !this.Data[1].Length
      {
        this.MessagePos:=this.DrawString(&quot;Нет данных&quot;,this.GPosW//2-this.MessagePos.W//2,this.GPosH//2-this.MessagePos.H//2,,,,this.Font_Colour,this.Font_Back)
        if !this.GuiHiden
          this.Update()
        ObjRelease(ObjPtr(this))
        return
      }
      ArAr:=isObject(this.Data[1][1])
      if (!ArAr and this.Data[1].Has(2))
        ArAr:=strlen(this.Data[1][2])!=14
    }
    Ars:=ArAr?this.Data.Length:1

    loop Ars
    {
      this.Proc:=a_index
      if (this.prevArrSize.Length&lt;this.Proc)
      {
        if (this.Proc&gt;10)
          this.ArrName.Push(&quot;&quot;)
        this.prevArrSize.Push(1)
        this.SearchStop.Push(true)
        this.SearchSkip.Push(0)
        this.LineW.Push(1.0)
        this.Transp.Push(255)
        this.Smooth.Push(true)
        this.AutoScale.Push(true)
        this.SaveAutoScale.Push(false)
        this.GHide.Push(false)
        this.YScale.Push(1)
        this.SaveYScale.Push(1)
        this.Pen.Push(&quot;&quot;)
        this.Hue.Push(512)
        this.ArrSize.Push(0)
        this.prev_ArrSize.Push(0)
        this.LockYP.Push(0)
        this.LockYM.Push(0)
        this.Val_UnderGraph_Drawed.Push(false)
        this.UnderGX.Push(0)
        this.UnderGY.Push(0)
        this.UnderGV.Push(0)
        this.Lock_hi_lim.Push(false)
        this.Lock_lo_lim.Push(false)
        this.StampT.Push([this.CreateTime])
        this.TimeStampExist.Push(false)

        this.UpdateMenu()
      }

      if (this.Pen[this.Proc]=&quot;&quot;)
      {
        Hue:=random(0,255)
        if (this.Proc&gt;1 and this.Hue[this.Proc-1])
        {
          loop 100
          {
            Hue:=random(0,255)
            free:=true
            for v in this.Hue
            {
              if (abs(v-Hue)&lt;30)
              {
                free:=false
                break
              }
            }
            if free
              break
          }
        }
        this.Pen[this.Proc]:=this.AHSVToARGB(this.Transp[this.Proc],Hue,255,192)
        this.Hue[this.Proc]:=Hue
      }

      if (this.SearchSkip[this.Proc] and this.Old_XTOffset&gt;this.XTOffset)
      {
        this.SearchStop[this.Proc]:=true
        this.SearchSkip[this.Proc]:=0
      }
    }
    this.Old_XTOffset:=this.XTOffset


    XMarker:=[false,false]
    XMarkeri:=[0,0]
    MarkerTime:=[1601,1601]
    MarkerTimeMs:=[0,0]
    XMarkerCoord:=[0,0]
    MarkerValue:=[&quot;&quot;,&quot;&quot;]
    MarkerText:=[&quot;&quot;,&quot;&quot;]
    MarkerDText:=&quot;&quot;
    MarkerMin:=0
    MarkerMax:=0
    StDeviation:=&quot;&quot;
    MarkerMean:=&quot;&quot;
    StampPos:={X:0,Y:0,W:0,H:this.GPosH+this.MessagePos.H*2,Chars:0,Lines:0}
    prevStampPos:={X:0,Y:0,W:0,H:this.GPosH,Chars:0,Lines:0}
    Stamp:=false
    GetSizeOnce:=false
    prevCoordY:=0
    ValNum:=0
    Deviation:=0

    this.ValTipPos:={X:0,Y:0,W:0,H:0,Chars:0,Lines:0}
    PrevtmpY:=0
    loop________Start:
    loop Ars
    {
      this.Proc:=a_index
      if ArAr
        this.pData:=this.Data[this.Proc]
      else
        this.pData:=this.Data

      ArrSize:=this.pData.Length
      this.ArrSize[this.Proc]:=ArrSize

      this.TimeStampMillis:=false
      if (ArrSize and IsObject(this.pData[ArrSize]))
      {
        if this.pData[ArrSize].Has(2)
          this.TimeStampExist[this.Proc]:=strlen(this.pData[ArrSize][2])=14

        if this.pData[ArrSize].Has(3)
          this.TimeStampMillis:=this.pData[ArrSize][3]!=&quot;&quot;
      }
; Добавление временной метки массиву без неё
      if !this.TimeStampExist[this.Proc]
      {
        if (ArrSize&gt;this.prevArrSize[this.Proc] and ArrSize)
        {
          tmp2:=this.StampT[this.Proc][this.prevArrSize[this.Proc]]
          tmp3:=this.TimeSub([A_Now,A_MSec],tmp2)
          tmp1:=ArrSize-this.prevArrSize[this.Proc]
          tmp3:=tmp3//tmp1
          if (tmp3=0)
            tmp3:=1
          loop tmp1
          {
            add:=a_index*tmp3
            this.StampT[this.Proc].Push(this.TimeAddMs(tmp2,add))
          }
          this.prevArrSize[this.Proc]:=ArrSize
        }
      }

      Period:=this.GPosW/this.ZoomX ; количество пикселей на точку по X
      PerioT:=this.GPosW/this.ZoomT ; количество пикселей на миллисекунду по X
      this.PerioT:=PerioT
      if this.YScale[this.Proc]
        Pixel:=this.GPosH/this.YScale[this.Proc] ; сколько пикселей занимает 1 единица по Y
      else
        Pixel:=1
      ZeroPos:=this.ZeroPosU
; Пауза скролинга для режима отображения по точкам
      if (this.Pause and this.Proc=this.Sel and this.prev_ArrSize[this.Proc] and ArrSize!=this.prev_ArrSize[this.Proc])
      {
        tmp1:=ArrSize-this.prev_ArrSize[this.Proc]
        this.X_Offset+=tmp1
        this.OldX_Offset+=tmp1
      }
      this.prev_ArrSize[this.Proc]:=ArrSize

; Перемещение графика
      L_Button:
      if (MouseIn and mY&lt;this.GPosH and getkeystate(&quot;LButton&quot;,&quot;P&quot;) and !this.Setting)
      {
        if !this.GetmCoordOnce
        {
          this.LButtonStart:=a_tickcount
          this.Old_mX:=mX
          this.Old_mY:=mY
          this.OldX_Offset:=this.X_Offset
          this.OldXTOffset:=this.XTOffset
          this.GetmCoordOnce:=true
          this.OffsetSetting:=true
          this.ShiftStrt:=true
        }
        this.X_Offset:=this.OldX_Offset-round((this.Old_mX-mX)/Period)
        this.XTOffset:=this.OldXTOffset-round((this.Old_mX-mX)/PerioT)

        DeltaY:=this.Old_mY-mY
        if (this.Sel=this.Proc)
          this.ZeroPos+=DeltaY
        if (this.Sel=this.Proc)
          this.Old_mY:=mY

        if (this.OldX_Offset-this.X_Offset!=0)
          this.MouseMoved:=true
      }
      else if (this.GetmCoordOnce and !getkeystate(&quot;LButton&quot;,&quot;P&quot;))
      {
        this.OffsetSetting:=false
        ; Добавление координаты маркеру
        if !this.MouseMoved
          this.XMarker[1]:=mX
        this.MouseMoved:=false
        this.GetmCoordOnce:=false
      }
      if !getkeystate(&quot;LButton&quot;,&quot;P&quot;)
        this.SearchStop[this.Proc]:=false

; Выделение участка
      R_Button:
      if (MouseIn and mY&lt;this.GPosH and getkeystate(&quot;RButton&quot;,&quot;P&quot;) and !this.Setting)
      {
        if !this.GetStartPosRectOnce
        {
          this.GetStartPosRectOnce:=true
          this.RectXs:=mX
          this.RectYs:=mY
        }
        if (this.RectXs&gt;mX)
          this.RectX:=mX
        else
          this.RectX:=this.RectXs
        if (this.RectYs&gt;mY)
          this.RectY:=mY
        else
          this.RectY:=this.RectYs
        this.RectW:=abs(this.RectXs-mX)
        this.RectH:=abs(this.RectYs-mY)
        if (this.RectW&gt;10 and this.RectH&gt;10)
          this.DrawRectangle(this.Font_Colour,this.RectX,this.RectY,this.RectW,this.RectH)
        else
          this.DrawRectangle(this.GrayPen2,this.RectX,this.RectY,this.RectW,this.RectH)
      }
      else if (this.GetStartPosRectOnce and !getkeystate(&quot;RButton&quot;,&quot;P&quot;))
      {
        this.GetStartPosRectOnce:=false
        if (this.RectW&gt;10 and this.RectH&gt;10)
        {
          this.RectZoom:=true
          this.SaveZoomX:=this.ZoomX
          this.SaveZoomT:=this.ZoomT
          this.SaveX_Offset:=this.X_Offset
          this.SaveXTOffset:=this.XTOffset
          this.SavePause:=this.Pause
          this.Pause:=true
          this.ShiftStrt:=true
          loop Ars
          {
            this.SaveAutoScale[a_index]:=this.AutoScale[a_index]
            this.SaveZeroPos:=this.ZeroPos
            this.SaveYScale[a_index]:=this.YScale[a_index]
            if this.AutoScale[a_index]
            {
              this.AutoScale[a_index]:=false
            }
            P:=this.GPosH/this.YScale[a_index] ; сколько пикселей занимает 1 единица по Y
            this.YScale[a_index]:=this.RectH/P
          }
          this.UpdateMenu()
          U:=this.SaveYScale[this.Sel]/this.GPosH ; сколько единиц занимает 1 пиксель по Y
          P:=this.GPosH/this.YScale[this.Sel] ; сколько пикселей занимает 1 единица по Y
          RectY2:=(this.RectY+this.RectH)
          this.ZeroPos-=((this.ZeroPosU-RectY2)*U)*P+(this.GPosH-this.ZeroPosU)
          this.ZeroPos:=round(this.ZeroPos)
          if !this.ScaleXByTime
          {
            this.ZoomX:=round(this.RectW/Period)+1
            this.X_Offset+=round((this.GPosW-(this.RectX+this.RectW))/Period)
          }
          else
          {
            this.ZoomT:=round(this.RectW/PerioT)
            this.XTOffset+=round((this.GPosW-(this.RectX+this.RectW))/PerioT)
          }
        }
        else if this.RectZoom
        {
          this.RectZoom:=false
          this.ZoomX:=this.SaveZoomX
          this.ZoomT:=this.SaveZoomT
          this.X_Offset:=this.SaveX_Offset
          this.XTOffset:=this.SaveXTOffset
          this.Pause:=this.SavePause
          this.ShiftStrt:=true
          this.ZeroPos:=this.SaveZeroPos
          loop Ars
          {
            this.YScale[a_index]:=this.SaveYScale[a_index]
            if ((this.SaveAutoScale[a_index] and !this.AutoScale[a_index]) or (!this.SaveAutoScale[a_index] and this.AutoScale[a_index]))
            {
              this.AutoScale[a_index]:=true
              this.UpdateMenu()
            }
          }
        }
      }

; Обработать колесо мыши
      Whell:
      if (MouseIn and (Graph.WheelUp or Graph.WheelDown) and Graph.HotKeymW=this.GuiGraph.Hwnd and this.Proc=1)
      {
        CoefHi:=mY/this.GPosH
        CoefLo:=(this.GPosH-mY)/this.GPosH
        tmp1:=round(this.ZoomX/3)+1
        tmt1:=round(this.ZoomT/3)+1
        if Graph.WheelUp
        {
          this.ZoomX-=tmp1
          this.ZoomT-=tmt1
        }
        else if Graph.WheelDown
        {
          this.ZoomX+=tmp1
          this.ZoomT+=tmt1
        }
        Graph.WheelUp:=false
        Graph.WheelDown:=false
        if (this.ZoomX&lt;10)
          this.ZoomX:=10
        if (this.ZoomX&gt;100000)
          this.ZoomX:=100000
        if (this.ZoomT&lt;1000)
          this.ZoomT:=1000
        if (this.ZoomT&gt;Graph.MaxTime)
          this.ZoomT:=Graph.MaxTime
        this.ShiftStrt:=true
        this.Pause:=true
        tmp1:=(this.GPosW-mX)/Period
        tmp2:=(this.GPosW-mX)/(this.GPosW/this.ZoomX)
        this.X_Offset+=round(tmp1-tmp2)
        tmp1:=(this.GPosW-mX)/PerioT
        tmp2:=(this.GPosW-mX)/(this.GPosW/this.ZoomT)
        this.XTOffset+=round(tmp1-tmp2)
      }

; ограничить перемещение графика
      if (this.Proc=this.Sel)
      {
        if !this.ScaleXByTime
        {
          tmp1:=this.ZoomX//2
          if (this.X_Offset&lt;-tmp1)
            this.X_Offset:=-tmp1
          else if (this.X_Offset&gt;0 and this.X_Offset&gt;this.ArrSize[this.Proc]-tmp1)
            this.X_Offset:=this.ArrSize[this.Proc]-tmp1
        }
        else
        {
          tmp1:=this.ZoomT//2
          if this.TimeStampExist[this.Proc]
          {
            if this.TimeStampMillis
            {
              tmp2:=this.TimeSub([A_Now,A_MSec],[this.pData[this.pData.Length][2],this.pData[this.pData.Length][3]])
              tmp3:=this.TimeSub([A_Now,A_MSec],[this.pData[1][2],this.pData[1][3]])
            }
            else
            {
              tmp2:=this.TimeSub([A_Now,A_MSec],[this.pData[this.pData.Length][2],0])
              tmp3:=this.TimeSub([A_Now,A_MSec],[this.pData[1][2],0])
            }
          }
          else
          {
            tmp2:=this.TimeSub([A_Now,A_MSec],[this.StampT[this.Proc][this.StampT[this.Proc].Length][1],this.StampT[this.Proc][this.StampT[this.Proc].Length][2]])
            tmp3:=this.TimeSub([A_Now,A_MSec],[this.StampT[this.Proc][1][1],this.StampT[this.Proc][1][2]])
          }
          tmp2-=tmp1
          tmp3-=tmp1
          if (this.XTOffset&lt;-tmp1)
          {
            this.XTOffset:=-tmp1
            this.ShiftStrt:=true
          }
          else if (this.XTOffset&lt;tmp2)
          {
            this.XTOffset:=tmp2
            this.ShiftStrt:=true
          }
          else if (this.XTOffset&gt;0 and this.XTOffset&gt;tmp3)
          {
            this.XTOffset:=tmp3
            this.ShiftStrt:=true
          }
;          this.XTOffset:=Integer(this.XTOffset)
        }
      }

; отрисовать сетку
      if (this.Sel=this.Proc)
      {
        tmpc:=this.AHSVToARGB(200,this.Hue[this.Proc],this.Hue[this.Proc]&gt;255?0:100,this.Hue[this.Proc]&gt;255?(this.Hue[this.Proc]&gt;&gt;8):255)
        LineH:=this.Pos.H*2
        RoundTo:=1
        GoAgain:
        Drawed:=0
        if (ZeroPos&lt;=this.GPosH+1 and ZeroPos&gt;=0)
        {
          loop ZeroPos//LineH
          {
            tmp1:=a_index*LineH
            tmp2:=tmp1/Pixel
            Val:=round(tmp2,-(strlen(round(tmp2))-RoundTo))
            if (Val=0)
              continue
            CoordY:=ZeroPos-Val*Pixel
            if (CoordY&lt;0)
              break
            this.DrawString(&quot; &quot; Val,0,CoordY,,,,this.Font_Colour,tmpc)
            this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
          }
          loop (this.GPosH-ZeroPos)//LineH
          {
            tmp1:=a_index*LineH
            tmp2:=tmp1/Pixel
            Val:=round(tmp2,-(strlen(round(tmp2))-RoundTo))
            if (Val=0)
              continue
            CoordY:=ZeroPos+Val*Pixel
            if (CoordY&gt;this.GPosH)
              break
            this.DrawString(&quot;-&quot; Val,0,CoordY-this.Pos.H,,,,this.Font_Colour,tmpc)
            this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
          }
        }
        else if (ZeroPos&gt;this.GPosH)
        {
          loop this.GPosH//LineH
          {
            tmp1:=ZeroPos-a_index*LineH
            tmp2:=tmp1/Pixel
            Val:=round(tmp2,-(strlen(round(tmp2))-RoundTo))
            CoordY:=ZeroPos-Val*Pixel
            if (prevCoordY!=CoordY and CoordY&lt;=this.GPosH-this.Pos.H and CoordY&gt;=0)
            {
              this.DrawString(&quot; &quot; Val,0,CoordY,,,,this.Font_Colour,tmpc)
              this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
              Drawed++
            }
            prevCoordY:=CoordY
          }
          if (Drawed&lt;1)
          {
            RoundTo++
            if (RoundTo&lt;10)
              goto GoAgain
            tmp1:=ZeroPos-this.GPosH/2
            tmp2:=tmp1/Pixel
            Val:=round(tmp2)
            CoordY:=ZeroPos-Val*Pixel
            this.DrawString(&quot; &quot; Val,0,CoordY,,,,this.Font_Colour,tmpc)
            this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
          }
        }
        else if (ZeroPos&lt;0)
        {
          loop this.GPosH//LineH
          {
            tmp1:=-ZeroPos+a_index*LineH
            tmp2:=tmp1/Pixel
            Val:=round(tmp2,-(strlen(round(tmp2))-RoundTo))
            CoordY:=ZeroPos+Val*Pixel
            if (prevCoordY!=CoordY and CoordY&lt;=this.GPosH+1 and CoordY&gt;=this.Pos.H)
            {
              this.DrawString(&quot;-&quot; Val,0,CoordY-this.Pos.H,,,,this.Font_Colour,tmpc)
              this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
              Drawed++
            }
            prevCoordY:=CoordY
          }
          if (Drawed&lt;1)
          {
            RoundTo++
            if (RoundTo&lt;10)
              goto GoAgain
            tmp1:=-ZeroPos+this.GPosH/2
            tmp2:=tmp1/Pixel
            Val:=round(tmp2)
            CoordY:=ZeroPos+Val*Pixel
            this.DrawString(&quot;-&quot; Val,0,CoordY-this.Pos.H,,,,this.Font_Colour,tmpc)
            this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
          }
        }
; отрисовать линию и текст нуля
        if (ZeroPos&lt;=this.GPosH+1 and ZeroPos&gt;=0)
        {
          this.DrawLine(this.GrayPen,0,ZeroPos,this.GPosW,ZeroPos)
          tmp0:=ZeroPos+this.Pos.H//2
          tmp1:=(this.GPosH&gt;tmp0) ? ZeroPos-this.Pos.H//2 : this.GPosH-this.Pos.H
          this.DrawString(&quot;0&quot;,0,tmp1,,,,this.Font_Colour,tmpc)
        }
      }
; сетка отрисована

      if (!ArrSize and this.Proc=this.Sel)
        this.MessagePos:=this.DrawString(&quot;Нет данных&quot;,this.GPosW//2-this.MessagePos.W//2,this.GPosH//2-this.MessagePos.H//2,,,,this.Font_Colour,this.Font_Back)
      if (this.SearchSkip[this.Proc]&gt;0)
        this.SearchSkip[this.Proc]--

; Отрисовка графика ----------------------------------------------------------------
      hi_lim:=Graph.min
      lo_lim:=Graph.max
;      Oldi:=1
      NotScaleXByTime:
      this.LockYP[this.Proc]:=0
      this.LockYM[this.Proc]:=0
      StampLine:=this.StampPos.W/2
      this.Val_UnderGraph_Drawed[this.Proc]:=false
      if (!this.ScaleXByTime and ArrSize)
      {
        Old_CoordY:=ZeroPos
        Old_CoordX:=this.GPosW+10
        GPoints:=[[Old_CoordX,Old_CoordY]]
        Aindex:=0
        Oldi:=ArrSize
        if !this.GHide[this.Proc]
        loop this.GPosW
        {
          iLast:=a_index=this.GPosW
          i:=Floor(ArrSize-(a_index/Period+1))-this.X_Offset
          if (i&gt;ArrSize and !iLast)
            continue
          if (i&lt;1)
            i:=1
          if iLast
          {
            if (i-1&gt;0)
              i--
            else
              i:=Oldi
            if (i+1&gt;ArrSize)
              continue
          }
          Aindex++
          if (Aindex=1 and i+1&lt;=ArrSize and i&gt;0)
          {
            if !this.TimeStampExist[this.Proc]
              Value:=this.pData[i]
            else
              Value:=this.pData[i][1]
            tmp1:=Value?Value:0
            Value:=Value!=&quot;&quot;?Value:&quot;Null&quot;
            if (tmp1!=&quot;&quot;)
            {
              hi_lim:=tmp1&gt;hi_lim ? tmp1 : hi_lim
              lo_lim:=tmp1&lt;lo_lim ? tmp1 : lo_lim
            }
            CoordY:=ZeroPos-tmp1*Pixel
            if (CoordY&lt;-1)
              CoordY:=0
            else if (CoordY&gt;this.GPosH+1)
              CoordY:=this.GPosH
            GPoints.Push([this.GPosW,CoordY])
          }
          if !this.TimeStampExist[this.Proc]
            Value:=this.pData[i]
          else
            Value:=this.pData[i][1]
          tmp1:=Value?Value:0
          Value:=Value!=&quot;&quot;?Value:&quot;Null&quot;
          if (tmp1!=&quot;&quot;)
          {
            hi_lim:=tmp1&gt;hi_lim ? tmp1 : hi_lim
            lo_lim:=tmp1&lt;lo_lim ? tmp1 : lo_lim
          }
          CoordX:=this.GPosW-a_index
          CoordY:=ZeroPos-tmp1*Pixel
          SkipLine:=false
          if (CoordY&lt;-1)
          {
            CoordY:=0
            SkipLine:=true
            this.LockYP[this.Proc]++
          }
          else if (CoordY&gt;this.GPosH+1)
          {
            CoordY:=this.GPosH
            SkipLine:=true
            this.LockYM[this.Proc]++
          }
          NewStamp:=false
          NewXMarker:=false
          if (Oldi!=i)
          {
            ; линия графика
            if !this.Smooth[this.Proc]
            {
              GPoints.Push([Old_CoordX,CoordY])
              Old_CoordY:=CoordY
            }
            GPoints.Push([CoordX,CoordY])
            ; Отрисовка подробностей при малом увеличении
            if (Oldi-i&gt;1 and !this.TimeOut)
            {
              max:=Graph.min
              min:=Graph.max
              loop Oldi-i+1
              {
                j:=i+a_index-1
                if (j&lt;1 or j&gt;ArrSize)
                  continue
                if !this.TimeStampExist[this.Proc]
                  tmp1:=this.pData[j]
                else
                  tmp1:=this.pData[j][1]
                min:=min&lt;tmp1?min:tmp1
                max:=max&gt;tmp1?max:tmp1
                if (tmp1!=&quot;&quot;)
                {
                  hi_lim:=tmp1&gt;hi_lim ? tmp1 : hi_lim
                  lo_lim:=tmp1&lt;lo_lim ? tmp1 : lo_lim
                }
              }
              min:=ZeroPos-min*Pixel
              max:=ZeroPos-max*Pixel
              if (min&lt;0)
                min:=1
              if (min&gt;this.GPosH)
                min:=this.GPosH
              if (max&lt;0)
                max:=0
              if (max&gt;this.GPosH)
                max:=this.GPosH-1
              GPoints.Push([CoordX,min])
              GPoints.Push([CoordX,max])
              GPoints.Push([CoordX,CoordY])
            }
            ; Дорисовать точки при большом увеличении
            if (Old_CoordX-CoordX&gt;3)
              this.DrawEllipse(this.Pen[this.Proc],CoordX-1,CoordY-1,2,2,this.LineW[this.Proc])

            ; текст значения под курсором
            if (!this.Val_UnderGraph_Drawed[this.Proc] and MouseIn)
            if (mX&gt;=CoordX and mX&lt;=CoordX+20)
            if (mY&gt;CoordY and mY&lt;CoordY+20)
            {
              this.Val_UnderGraph_Drawed[this.Proc]:=true
              this.UnderGX[this.Proc]:=CoordX
              this.UnderGY[this.Proc]:=CoordY
              this.UnderGV[this.Proc]:=Value
            }
            Old_CoordX:=CoordX
            Old_CoordY:=CoordY
            Oldi:=i
            NewStamp:=true
            if (this.Sel=this.Proc)
              NewXMarker:=true
          }
          ; линия и текст временой шкалы
          if (this.Sel=this.Proc)
          {
            if Period&gt;=1
              StampLine++
            else
              StampLine+=1
            tmp1:=this.StampPos.W+2
            if (NewStamp and StampLine&gt;tmp1) or (!Stamp and a_index=this.GPosW)
            {
              StampLine:=0
              Stamp:=true
              this.DrawLine(this.GrayPen2,CoordX,0,CoordX,this.GPosH)
              if !this.TimeStampExist[this.Proc]
              {
                tmp10:=FormatTime(this.StampT[this.Proc][i][1],&quot;HH:mm:ss&quot;)
                tmp10.=&quot;`n&quot; format(&quot;{:03}&quot;,this.StampT[this.Proc][i][2]) &quot;ms&quot;
              }
              else
              {
                tmp10:=FormatTime(this.pData[i][2],&quot;dd.MM.yyyy&#039;`n&#039;HH:mm:ss&quot;)
                if (this.TimeStampMillis and this.pData[i][3]!=&quot;&quot;)
                  tmp10.=&quot;.&quot; format(&quot;{:03}&quot;,this.pData[i][3])
              }
              StampPos:=this.DrawString(tmp10,CoordX-this.StampPos.W/2,this.GPosH,,,,this.Font_Colour,this.GrayPen2)
              if !GetSizeOnce
              {
                GetSizeOnce:=true
                this.StampPos:=StampPos
                this.StampOffset:=this.StampPos.H
              }
              if (prevStampPos.w)
              {
                if (this.StampPos.X+this.StampPos.w&gt;prevStampPos.X)
                  this.StampsNum--
                if (this.StampPos.X+this.StampPos.w*2+5&lt;prevStampPos.X)
                  this.StampsNum++
                if this.StampsNum&lt;2
                  this.StampsNum:=2
              }
              prevStampPos:=this.StampPos
            }
            ; маркеры
            tmp1:=this.GPosW-a_index
            loop 2
            {
              if (NewXMarker and !XMarker[a_index] and tmp1&lt;this.XMarker[a_index])
              {
                XMarker[a_index]:=true
                XMarkeri[a_index]:=i
                XMarkerCoord[a_index]:=tmp1
              }
            }
            NewXMarker:=false
          }
        }
        ; рисовать линию графика
        if !this.GHide[this.Proc]
          this.DrawLines(this.Pen[this.Proc],GPoints,this.LineW[this.Proc])
        ; блокировать перемещение по Y
        this.LockYP[this.Proc]:=(this.LockYP[this.Proc]&gt;=Aindex-3)?1:0
        this.LockYM[this.Proc]:=(this.LockYM[this.Proc]&gt;=Aindex-3)?1:0
      }
      ScaleXByTime:
      if (this.ScaleXByTime and ArrSize)
      {
        Strt:=this.TimeSubMs([A_Now,A_MSec],this.XTOffset)
        if (this.Pause and (!this.SaveStrt[1] or this.OffsetSetting or this.ShiftStrt))
        {
          this.SaveStrt:=Strt
          this.ShiftStrt:=false
        }
        else if (!this.Pause and this.SaveStrt[1])
          this.SaveStrt:=[0,0]
        if this.Pause
          Strt:=this.SaveStrt
        Endt:=this.TimeSubMs(Strt,this.ZoomT)
        ; линия и текст временой шкалы
        if (this.Sel=this.Proc)
        {
          loop this.GPosW
          {
            CoordX:=this.GPosW-(a_index-1)
            StampLine++
            tmp1:=this.StampPos.W+2
            if (StampLine&gt;tmp1) or (!Stamp and a_index=this.GPosW)
            {
              Stamp:=true
              StampLine:=0
              this.DrawLine(this.GrayPen2,CoordX,0,CoordX,this.GPosH)
              tmp2:=(this.ZoomT/this.GPosW)*(a_index-1)
              tmp1:=this.TimeSubMs(Strt,tmp2)
              tmp10:=FormatTime(tmp1[1],&quot;dd.MM.yyyy&#039;`n&#039;HH:mm:ss&quot;)
              tmp10.=&quot;.&quot; tmp1[2]
              StampPos:=this.DrawString(tmp10,CoordX-this.StampPos.W/2,this.GPosH,,,,this.Font_Colour,this.GrayPen2)
              if !GetSizeOnce
              {
                GetSizeOnce:=true
                this.StampPos:=StampPos
                this.StampOffset:=this.StampPos.H
              }
              if (prevStampPos.w)
              {
                if (this.StampPos.X+this.StampPos.w&gt;prevStampPos.X)
                  this.StampsNum--
                if (this.StampPos.X+this.StampPos.w*2+5&lt;prevStampPos.X)
                  this.StampsNum++
                if this.StampsNum&lt;2
                  this.StampsNum:=2
              }
              prevStampPos:=this.StampPos
            }
          }
        }
        ; найти первую точку
        iStart:=1
        Same:=0
        if !this.GHide[this.Proc]
        loop ArrSize
        {
          i:=ArrSize-(a_index-1)-this.SearchSkip[this.Proc]
          if (i&lt;1)
          {
            iStart:=1
            break
          }
          if !this.TimeStampExist[this.Proc]
          {
            iTime:=this.StampT[this.Proc][i][1]
            iTimeMs:=this.StampT[this.Proc][i][2]
            if (iTime&gt;Strt[1] or (iTime=Strt[1] and iTimeMs&gt;=Strt[2]))
              continue
            else
            {
              iStart:=i=ArrSize?i:i+1
              break
            }
          }
          else
          {
            iTime:=this.pData[i][2]
            if this.TimeStampMillis
              iTimeMs:=this.pData[i][3]
            else
            {
              if !Same
              {
                iTimeMs:=0
                loop 1000
                {
                  j:=1+i-a_index
                  if (j&gt;0 and iTime=this.pData[j][2])
                    Same++
                  else
                    break
                }
                SameT:=1000/(Same+1)
              }
              if (Same&gt;0)
              {
                iTimeMs:=round(SameT*Same)
                Same--
              }
            }
            if (iTime&gt;Strt[1] or (this.TimeStampMillis and iTime=Strt[1] and iTimeMs&gt;=Strt[2]))
              continue
            else
            {
              iStart:=i=ArrSize?i:i+1
              break
            }
          }
        }
        Old_CoordY:=ZeroPos
        Old_CoordX:=this.GPosW+10
        GPoints:=[[Old_CoordX,Old_CoordY]]
        SearchSkip:=this.SearchSkip[this.Proc]?this.SearchSkip[this.Proc]:0
        Same:=0
        Aindex:=0
        Oldi:=iStart
        iLast:=false
        if !this.GHide[this.Proc]
        while !iLast ;loop iStart
        {
          i:=iStart-(a_index-1)
          if (i&lt;1)
          {
            i:=1
            iLast:=true
          }
          if !this.TimeStampExist[this.Proc]
          {
            iTime:=this.StampT[this.Proc][i][1]
            iTimeMs:=this.StampT[this.Proc][i][2]
          }
          else
          {
            iTime:=this.pData[i][2]
            if this.TimeStampMillis
              iTimeMs:=this.pData[i][3]
            else
            {
              if !Same
              {
                iTimeMs:=0
                loop 1000
                {
                  j:=i-a_index
                  if (j&gt;0 and iTime=this.pData[j][2])
                    Same++
                  else
                    break
                }
                SameT:=1000/(Same+1)
              }
              if (Same&gt;0)
              {
                iTimeMs:=round(SameT*Same)
                Same--
              }
            }
          }
          if (iTime&lt;Endt[1])
            iLast:=true
          mms:=Strt[2]-iTimeMs
          if (mms&lt;0)
          {
            mms+=1000
            iTime:=DateAdd(iTime,1,&quot;Seconds&quot;)
          }
          ttt:=DateDiff(Strt[1],iTime,&quot;Seconds&quot;)
          CoordX:=this.GPosW-round(PerioT*(ttt*1000+mms))
          Aindex++
          if (Aindex=1 and !this.SearchStop[this.Proc])
          {
            tmp1:=ArrSize-i
            if (SearchSkip&lt;tmp1)
              SearchSkip:=tmp1
          }
          if (CoordX!=Old_CoordX or iLast)
          {
            if !this.TimeStampExist[this.Proc]
              Value:=this.pData[i]
            else
              Value:=this.pData[i][1]
            tmp1:=Value?Value:0
            if (Value!=&quot;&quot; and Aindex)
            {
              hi_lim:=Value&gt;hi_lim ? Value : hi_lim
              lo_lim:=Value&lt;lo_lim ? Value : lo_lim
            }
            Value:=Value!=&quot;&quot;?Value:&quot;Null&quot;
            CoordY:=ZeroPos-tmp1*Pixel
            SkipLine:=false
            if (CoordY&lt;-1)
            {
              CoordY:=0
              SkipLine:=true
              this.LockYP[this.Proc]++
            }
            else if (CoordY&gt;this.GPosH+1)
            {
              CoordY:=this.GPosH
              SkipLine:=true
              this.LockYM[this.Proc]++
            }
            ; линия графика
            if !this.Smooth[this.Proc]
            {
              GPoints.Push([Old_CoordX,CoordY])
              Old_CoordY:=CoordY
            }
            GPoints.Push([CoordX,CoordY])
            ; Отрисовка подробностей при малом увеличении
            if (Oldi-i&gt;1 and !this.TimeOut)
            {
              max:=Graph.min
              min:=Graph.max
              loop Oldi-i
              {
                j:=i+a_index-1
                if (j&lt;1 or j&gt;ArrSize)
                  continue
                if !this.TimeStampExist[this.Proc]
                  tmp1:=this.pData[j]
                else
                  tmp1:=this.pData[j][1]
                min:=min&lt;tmp1?min:tmp1
                max:=max&gt;tmp1?max:tmp1
                if (tmp1!=&quot;&quot;)
                {
                  hi_lim:=tmp1&gt;hi_lim ? tmp1 : hi_lim
                  lo_lim:=tmp1&lt;lo_lim ? tmp1 : lo_lim
                }
              }
              min:=ZeroPos-min*Pixel
              max:=ZeroPos-max*Pixel
              if (min&lt;0)
                min:=1
              if (min&gt;this.GPosH)
                min:=this.GPosH
              if (max&lt;0)
                max:=0
              if (max&gt;this.GPosH)
                max:=this.GPosH-1
              GPoints.Push([CoordX,min])
              GPoints.Push([CoordX,max])
              GPoints.Push([CoordX,CoordY])
            }
            ; Дорисовать точки при большом увеличении
            if (Old_CoordX-CoordX&gt;3)
              this.DrawEllipse(this.Pen[this.Proc],CoordX-1,CoordY-1,2,2,this.LineW[this.Proc])

            ; текст значения под курсором
            if (!this.Val_UnderGraph_Drawed[this.Proc] and MouseIn)
            if (mX&gt;=CoordX and mX&lt;=CoordX+20)
            if (mY&gt;CoordY and mY&lt;CoordY+20)
            {
              this.Val_UnderGraph_Drawed[this.Proc]:=true
              this.UnderGX[this.Proc]:=CoordX
              this.UnderGY[this.Proc]:=CoordY
              this.UnderGV[this.Proc]:=Value
            }
            Old_CoordX:=CoordX
            Old_CoordY:=CoordY
            Oldi:=i
            if iLast
              break
            ; маркеры
            if (this.Sel=this.Proc)
            {
              loop 2
              {
                if (!XMarker[a_index] and CoordX&lt;this.XMarker[a_index])
                {
                  XMarker[a_index]:=true
                  XMarkeri[a_index]:=i
                  if !this.TimeStampMillis
                    MarkerTimeMs[a_index]:=format(&quot;{:03}&quot;,iTimeMs)
                  XMarkerCoord[a_index]:=CoordX
                }
              }
            }
          }
        }
        ; рисовать линию графика
        if !this.GHide[this.Proc]
          this.DrawLines(this.Pen[this.Proc],GPoints,this.LineW[this.Proc])
        this.ZoomX:=Aindex?Aindex:1
        this.SearchSkip[this.Proc]:=SearchSkip

        ; блокировать перемещение по Y
        this.LockYP[this.Proc]:=(this.LockYP[this.Proc]&gt;=Aindex-3)?1:0
        this.LockYM[this.Proc]:=(this.LockYM[this.Proc]&gt;=Aindex-3)?1:0
      }
      ; Автомасштаб
      if (this.AutoScale[this.Proc] and !this.GHide[this.Proc] and (!this.OScale or this.Proc=this.Sel))
      {
        tmp1:=this.GPosH//2
        if (this.ZeroPosU&gt;tmp1 and this.ZeroPosU&lt;this.GPosH+1)
        {
          if (hi_lim&gt;0)
            this.YScale[this.Proc]:=abs(hi_lim)+(this.GPosH-this.ZeroPosU+10)/Pixel
          else
            this.YScale[this.Proc]:=-lo_lim+(this.ZeroPosU+10)/Pixel
        }
        else if (this.ZeroPosU&lt;=tmp1 and this.ZeroPosU&gt;0)
        {
          if (lo_lim&lt;0)
            this.YScale[this.Proc]:=-lo_lim+(this.ZeroPosU+10)/Pixel
          else
            this.YScale[this.Proc]:=abs(hi_lim)+(this.GPosH-this.ZeroPosU+10)/Pixel
        }
        if (this.YScale[this.Proc]=0)
          this.YScale[this.Proc]:=1
      }
      this.DrawLine(this.GrayPen2,0,this.GPosH,this.GPosW,this.GPosH)
      this.DrawLine(this.GrayPen2,0,this.GPosH+StampPos.H,this.GPosW,this.GPosH+StampPos.H)

      ; текст значения под курсором
      if this.Val_UnderGraph_Drawed[this.Proc]
      {
        this.DrawEllipse(0xffffffff,this.UnderGX[this.Proc]-3,this.UnderGY[this.Proc]-3,6,6,this.LineW[this.Proc])
        this.DrawEllipse(0xff000000,this.UnderGX[this.Proc]-2,this.UnderGY[this.Proc]-2,4,4,this.LineW[this.Proc])
        this.DrawEllipse(0xffffffff,this.UnderGX[this.Proc]-1,this.UnderGY[this.Proc]-1,2,2,this.LineW[this.Proc])
        tmpX:=this.UnderGX[this.Proc]+(5&lt;this.GPosW-this.ValTipPos.W)?this.UnderGX[this.Proc]+5:this.GPosW-this.ValTipPos.W
        tmpY:=(this.UnderGY[this.Proc]-this.Pos.H&lt;0)?0:(this.UnderGY[this.Proc]-this.Pos.H-5)
        if (tmpY-PrevtmpY&lt;this.ValTipPos.H)
        {
          if (mY&gt;this.GPosH//2)
            tmpY:=PrevtmpY-this.ValTipPos.H
          else
          {
            tmpY:=PrevtmpY+this.ValTipPos.H
            tmpX+=8
          }
        }
        tmpC:=this.AHSVToARGB(200,this.Hue[this.Proc],this.Hue[this.Proc]&gt;255?0:100,this.Hue[this.Proc]&gt;255?(this.Hue[this.Proc]&gt;&gt;8):255)
        this.ValTipPos:=this.DrawString((IsInteger(this.UnderGV[this.Proc])?this.UnderGV[this.Proc]:Format(&quot;{:0.&quot; this.FloatFormat &quot;f}&quot;,this.UnderGV[this.Proc])) &quot; &quot; this.ArrName[this.Proc],tmpX,tmpY,,,,this.Font_Colour,tmpC)
        PrevtmpY:=tmpY
      }
      if (this.ShowMarkers and this.Sel=this.Proc)
      {
        if (XMarkerCoord[1] or XMarkerCoord[2])
        {
          MarkerText[1]:=(this.ArrName[this.Proc]!=&quot;&quot;?this.ArrName[this.Proc]:&quot;Линия №&quot; this.Proc) &quot; Точек = &quot; abs(XMarkeri[1]-XMarkeri[2])+1 &quot;`nВремя        &quot;
          if this.TimeStampExist[this.Proc]
            loop 11
              MarkerText[1].=&quot; &quot;
          MarkerText[1].=&quot;Значение`n&quot;
        }
        loop 2
        {
          if XMarkerCoord[a_index]
          {
            if !this.TimeStampExist[this.Proc]
            {
              MarkerTime[a_index]:=this.StampT[this.Proc][XMarkeri[a_index]][1]
              MarkerTimeMs[a_index]:=this.StampT[this.Proc][XMarkeri[a_index]][2]
              tmp10:=FormatTime(MarkerTime[a_index],&quot;HH:mm:ss&quot;) &quot;.&quot; MarkerTimeMs[a_index]
              MarkerValue[a_index]:=this.pData[XMarkeri[a_index]]
              MarkerText[a_index].=tmp10 &quot; &quot; Format(&quot;{:0.&quot; this.FloatFormat &quot;f}&quot;,MarkerValue[a_index])
            }
            else
            {
              MarkerTime[a_index]:=this.pData[XMarkeri[a_index]][2]
              if (this.TimeStampMillis and this.pData[XMarkeri[a_index]][3]!=&quot;&quot;)
                MarkerTimeMs[a_index]:=this.pData[XMarkeri[a_index]][3]
              tmp10:=FormatTime(MarkerTime[a_index],&quot;dd.MM.yyyy HH:mm:ss&quot;) &quot;.&quot; MarkerTimeMs[a_index]
              MarkerValue[a_index]:=this.pData[XMarkeri[a_index]][1]
              MarkerText[a_index].=tmp10 &quot; &quot; Format(&quot;{:0.&quot; this.FloatFormat &quot;f}&quot;,this.pData[XMarkeri[a_index]][1])
            }
          }
        }
        if (XMarkerCoord[1] and XMarkerCoord[2])
        {
          i:=1, j:=2
          if (XMarkerCoord[1]&lt;XMarkerCoord[2])
            i:=2, j:=1
          ms:=MarkerTimeMs[i]-MarkerTimeMs[j]
          if (ms&lt;0)
            MarkerTime[j]:=DateAdd(MarkerTime[j],1,&quot;Seconds&quot;)
          MarkerDeltaT:=DateDiff(MarkerTime[i],MarkerTime[j],&quot;Seconds&quot;)
          MarkerDeltaT:=DateAdd(1601,MarkerDeltaT,&quot;Seconds&quot;)
          Y:=FormatTime(MarkerDeltaT,&quot;yyyy&quot;)
          M:=FormatTime(MarkerDeltaT,&quot;MM&quot;)
          D:=FormatTime(MarkerDeltaT,&quot;dd&quot;)
          Y:=format(&quot;{:04}&quot;,Y-1601)
          M:=format(&quot;{:02}&quot;,M-1)
          D:=format(&quot;{:02}&quot;,D-1)
          if this.TimeStampExist[this.Proc]
            MarkerDText.=D &quot;.&quot; M &quot;.&quot; Y &quot; &quot;
          tmp10:=FormatTime(MarkerDeltaT,&quot;HH:mm:ss&quot;)
          MarkerDText.=tmp10
          if MarkerTimeMs[i]
            MarkerDText.=&quot;.&quot; format(&quot;{:03}&quot;,abs(ms))

          MarkerMax:=Graph.min
          MarkerMin:=Graph.max
          ValNum:=XMarkeri[i]-XMarkeri[j]+1
          Summ:=0
          loop ValNum
          {
            if !this.TimeStampExist[this.Proc]
              Val:=this.pData[XMarkeri[j]+A_Index-1]
            else
              Val:=this.pData[XMarkeri[j]+A_Index-1][1]
            Summ+=Val
            if (Val&gt;MarkerMax)
              MarkerMax:=Val
            if (Val&lt;MarkerMin)
              MarkerMin:=Val
          }
          MarkerMean:=Summ/ValNum
          loop ValNum
          {
            if !this.TimeStampExist[this.Proc]
              Val:=this.pData[XMarkeri[j]+A_Index-1]
            else
              Val:=this.pData[XMarkeri[j]+A_Index-1][1]
            Deviation+=(Val-MarkerMean)**2
          }
        }
      }
    } ; Конец цикла
    Loop________End:

; общий масштаб
    if this.OScale
    {
      loop this.YScale.Length
      {
        if (a_index=this.Sel)
          continue
        this.YScale[a_index]:=this.YScale[this.Sel]
      }
    }

; Отрисовать меню
    OnGraphMenu:
    SelPos:=[]
    tmpc:=this.AHSVToARGB(200,this.Hue[1],this.Hue[1]&gt;255?0:100,this.Hue[1]&gt;255?(this.Hue[1]&gt;&gt;8):255)
    ; первая строка (этой строки может не быть)
    MenuSPY:=StampPos.Y+StampPos.H
    if (Ars&gt;1)
      Pos:=this.DrawString(&quot;1&quot;,0,MenuSPY,,,&quot;Center vCenter&quot;,this.GHide[1]?tmpc:this.Font_Colour,tmpc)
    else
      Pos:={X:0,Y:0,W:0,H:0,Chars:0,Lines:0}
    SelPos.Push(Pos)
    loop Ars
    {
      tmp1:=a_index+1
      if (tmp1&lt;=Ars)
      {
        tmpc:=this.AHSVToARGB(200,this.Hue[tmp1],this.Hue[tmp1]&gt;255?0:100,this.Hue[tmp1]&gt;255?(this.Hue[tmp1]&gt;&gt;8):255)
        Pos:=this.DrawString(tmp1,SelPos[a_index].X+SelPos[a_index].W+5,MenuSPY,,,&quot;Center vCenter&quot;,this.GHide[tmp1]?tmpc:this.Font_Colour,tmpc)
      }
      else
        Pos:=this.DrawString(&quot;Выбран &quot; this.Sel (this.ArrName[this.Sel]?&quot;-`&quot;&quot; this.ArrName[this.Sel] &quot;`&quot;&quot;:&quot;&quot;),SelPos[a_index].X+SelPos[a_index].W+5,MenuSPY,,,&quot;Center vCenter&quot;,this.Font_Colour,this.LightBlueBrush)
      SelPos.Push(Pos)
    }
    ; вторая строка (первая)
    MenuSPY:=StampPos.Y+StampPos.H+SelPos[1].H
    if !this.ScaleXByTime
      this.ZoomPos:=this.DrawString(&quot;Zoom X = &quot; this.ZoomX &quot; точек&quot;,0,MenuSPY,,,&quot;&quot;,this.Font_Colour,this.LightBlueBrush)
    else
    {
      tmp1:=this.MsToTime(this.ZoomT)
      tmp1[1]-=1000000
      tmp2:=FormatTime(tmp1[1],&quot;dd HH:mm:ss&quot;)
      tmp2.=&quot;.&quot; tmp1[2]
      this.ZoomPos:=this.DrawString(&quot;Zoom X = &quot; tmp2,0,MenuSPY,,,&quot;&quot;,this.Font_Colour,this.LightBlueBrush)
    }
    this.SmoothPos:=this.DrawString(this.Smooth[this.Sel]?&quot;~~~~&quot;:&quot;_-_-&quot;,this.ZoomPos.W+this.Pos.W,MenuSPY,,,&quot;&quot;,this.Font_Colour,this.LightBlueBrush)
    this.TransPos:=this.DrawString(&quot;&lt;&quot; this.Transp[this.Sel] &quot;&gt;&quot;,this.SmoothPos.X+this.SmoothPos.W+this.Pos.W,MenuSPY,,,&quot;&quot;,this.Font_Colour,this.LightBlueBrush)
    this.LineWPos:=this.DrawString(&quot;[&quot; this.LineW[this.Sel] &quot;]&quot;,this.TransPos.X+this.TransPos.W+this.Pos.W,MenuSPY,,,&quot;&quot;,this.Font_Colour,this.LightBlueBrush)
    this.FontSizePos:=this.DrawString(&quot;Шрифт &quot; this.FontSize,this.LineWPos.X+this.LineWPos.W+this.Pos.W,MenuSPY,,,&quot;&quot;,this.Font_Colour,this.LightBlueBrush)
    ; третья строка (вторая)
    MenuSPY+=this.ZoomPos.H
    this.AutoPos:=this.DrawString(&quot;Авто&quot;,0,MenuSPY,,,&quot;&quot;,this.AutoScale[this.Sel]?this.Font_Colour:this.GrayPen,this.LightBlueBrush)
    this.ScalePos:=this.DrawString(&quot;Шкала &quot; Format(&quot;{:0.&quot; this.FloatFormat &quot;f}&quot;,this.YScale[this.Sel]),this.AutoPos.X+this.AutoPos.W,MenuSPY,,,&quot;&quot;,this.Font_Colour,this.LightBlueBrush)
    if ArAr
      this.OScalePos:=this.DrawString(&quot; общая&quot;,this.ScalePos.X+this.ScalePos.W,MenuSPY,,,&quot;&quot;,this.OScale?this.Font_Colour:this.GrayPen,this.LightBlueBrush)
    BWPos:=this.DrawString(&quot;(&lt;&lt;)&quot;,this.ScalePos.X+this.ScalePos.W+this.OScalePos.W+15,MenuSPY,,,&quot;Center vCenter&quot;,this.Font_Colour,this.LightBlueBrush)
    PWPos:=this.DrawString(&quot;(||)&quot;,BWPos.X+BWPos.W+2,MenuSPY,,,&quot;Center vCenter&quot;,this.Font_Colour,this.LightBlueBrush)
    FWPos:=this.DrawString(&quot;(&gt;&gt;)&quot;,PWPos.X+PWPos.W+2,MenuSPY,,,&quot;Center vCenter&quot;,this.Font_Colour,this.LightBlueBrush)

    ; Размер меню
    tmp1:=(this.AutoPos.H&gt;this.ScalePos.H)?this.AutoPos.H:this.ScalePos.H
    this.MenuOffset:=this.ZoomPos.H+tmp1+SelPos[1].H

    ; хитбоксы
    SelHBA:=[]
    SelHB:=0
    SelSplitHB:=false
    loop Ars
    {
      SelHBA.Push(this.HitBox(mX,mY,SelPos[a_index]))
      if SelHBA[a_index]
      {
        SelHB:=a_index
        break
      }
    }
    if ArAr
      SelSplitHB:=this.HitBox(mX,mY,SelPos[Ars+1])
    LineWHB:=this.HitBox(mX,mY,this.LineWPos)
    TransHB:=this.HitBox(mX,mY,this.TransPos)
    FontSizeHB:=this.HitBox(mX,mY,this.FontSizePos)
    ZoomHB:=this.HitBox(mX,mY,this.ZoomPos)
    SmoothHB:=this.HitBox(mX,mY,this.SmoothPos)
    AutoHB:=this.HitBox(mX,mY,this.AutoPos)
    ScaleHB:=this.HitBox(mX,mY,this.ScalePos)
    OScaleHB:=this.HitBox(mX,mY,this.OScalePos)
    BWHB:=this.HitBox(mX,mY,BWPos)
    PWHB:=this.HitBox(mX,mY,PWPos)
    FWHB:=this.HitBox(mX,mY,FWPos)
    HB:=AutoHB or ScaleHB or ZoomHB or SmoothHB or LineWHB or TransHB or FontSizeHB
   or SelHB or OScaleHB or SelSplitHB or BWHB or PWHB or FWHB
    if (MouseIn and HB and !this.OffsetSetting and !this.Setting and A_Cursor=&quot;Arrow&quot;)
    {
      this.Setting:=true
      if AutoHB
      {
        TipText:=&quot;Автоматический масштаб  вкл./выкл.&quot;
        this.WhichSetting:=1
      }
      else if ScaleHB
      {
        TipText:=&quot;Масштаб шкалы`nЗажмите левую кн. мыши`n и потяните вверх/вниз`n или правой кнопкой мыши&quot;
        this.WhichSetting:=2
      }
      else if ZoomHB
      {
        TipText:=&quot;Масштаб по горизонтали`nЗажмите левую кн. мыши`n и потяните вверх/вниз`n или правой кнопкой мыши&quot;
        this.WhichSetting:=3
      }
      else if LineWHB
      {
        TipText:=&quot;Толщина линии графика`nЗажмите левую кн. мыши`n и потяните вверх/вниз`n или правой кнопкой мыши&quot;
        this.WhichSetting:=4
      }
      else if SelHB
      {
        TipText:=&quot;Нажмите левой кн. мыши чтобы`n выбрать график, настройки`n которого будут изменяться`nПравой кн. мыши - выключить график`n`n&quot; this.ArrName[SelHB]
        this.WhichSetting:=5
      }
      else if FontSizeHB
      {
        TipText:=&quot;Размер шрифта`nЗажмите левую кн. мыши`n и потяните вверх/вниз`n или правой кнопкой мыши&quot;
        this.WhichSetting:=6
      }
      else if SmoothHB
      {
        TipText:=&quot;Сглаживать ступеньки графика  вкл./выкл.&quot;
        this.WhichSetting:=7
      }
      else if OScaleHB
      {
        TipText:=&quot;Общий масштаб шкалы  вкл./выкл.&quot;
        this.WhichSetting:=8
      }
      else if SelSplitHB
      {
        TipText:=&quot;Нажмите чтобы отделить `nвыбранный график в отдельное окно`n`n&quot; this.ArrName[this.Sel]
        this.WhichSetting:=9
      }
      else if BWHB
      {
        TipText:=&quot;На 1 экран назад&quot;
        this.WhichSetting:=10
      }
      else if PWHB
      {
        TipText:=&quot;Пауза прокрутки&quot;
        this.WhichSetting:=11
      }
      else if FWHB
      {
        TipText:=&quot;На 1 экран вперед&quot;
        this.WhichSetting:=12
      }
      else if TransHB
      {
        TipText:=&quot;Видимость линии графика`nЗажмите левую кн. мыши`n и потяните вверх/вниз`n или правой кнопкой мыши&quot;
        this.WhichSetting:=13
      }
      this.TipPos:=this.DrawString(TipText,(this.GPosW-this.TipPos.W)//2,this.GPosH-this.TipPos.H,,,,this.Font_Colour,this.LightGreenBrush)
    }
    ; Изменение настроек
    if (getkeystate(&quot;LButton&quot;,&quot;P&quot;) and this.Setting)
    {
      if (this.WhichSetting=1)
      {
        this.AutoScale[this.Sel]:=!this.AutoScale[this.Sel]
        if this.OScale
          loop Ars
            this.AutoScale[a_index]:=this.AutoScale[this.Sel]
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=2)
      {
        this.YScale[this.Sel]+=(round(abs(this.YScale[this.Sel])/20)+1)*DeltaY
        this.YScale[this.Sel]:=round(this.YScale[this.Sel])
        this.AutoScale[this.Sel]:=false
      }
      else if (this.WhichSetting=3)
      {
        this.ZoomX+=(round(abs(this.ZoomX)/20)+1)*DeltaY
        this.ZoomX:=(this.ZoomX&gt;100000)?100000:(this.ZoomX&lt;10)?10:round(this.ZoomX)
        this.ZoomT+=(round(abs(this.ZoomT)/20)+1)*DeltaY
        this.ZoomT:=(this.ZoomT&gt;Graph.MaxTime)?Graph.MaxTime:(this.ZoomT&lt;1000)?1000:round(this.ZoomT)
      }
      else if (this.WhichSetting=4)
      {
        this.LineW[this.Sel]+=DeltaY/10
        if (this.LineW[this.Sel]&lt;1)
          this.LineW[this.Sel]:=1
        if (this.LineW[this.Sel]&gt;5)
          this.LineW[this.Sel]:=5
        this.LineW[this.Sel]:=round(this.LineW[this.Sel],1)
      }
      else if (this.WhichSetting=5)
      {
        loop Ars
        {
          if (SelHBA[a_index])
          {
            this.Sel:=a_index
            this.UpdateMenu()
            break
          }
        }
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=6)
      {
        this.FontSize+=DeltaY
        if (this.FontSize&lt;8)
          this.FontSize:=8
        if (this.FontSize&gt;70)
          this.FontSize:=70
        if (this.FontSize!=this.prevSize)
          this.SetFont(,this.FontSize)
      }
      else if (this.WhichSetting=7)
      {
        this.Smooth[this.Sel]:=!this.Smooth[this.Sel]
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=8)
      {
        this.OScale:=!this.OScale
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=9)
      {
        this.GuiGraph.GetPos(&amp;x,&amp;y)
        this.Child.Push(Graph(&quot;График `&quot;&quot; this.ArrName[this.Sel] &quot;`&quot;&quot;))
        this.Child[this.Child.Length].CreateTime:=this.CreateTime
        this.Child[this.Child.Length].Show(x+20,y+20)
        this.Parent++
        this.Child[this.Child.Length].IsChild:=this.Parent
        this.Child[this.Child.Length].DataArray:=this.Data[this.Sel]
        this.Child[this.Child.Length].ArrName[1]:=this.ArrName[this.Sel]
;        this.Child[this.Child.Length].         :=this.ArrName[this.Sel]
        this.Child[this.Child.Length].Parent:=this
        ObjRelease(ObjPtr(this))
        this.WhichSetting:=0
        this.GHide[this.Sel]:=true
        loop Ars
        {
          if !this.GHide[a_index]
            this.Sel:=a_index
        }
      }
      else if (this.WhichSetting=10)
      {
        if !this.ScaleXByTime
          this.X_Offset+=this.ZoomX
        else
          this.XTOffset+=this.ZoomT
        this.ShiftStrt:=true
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=11)
      {
        this.Pause:=!this.Pause
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=12)
      {
        if !this.ScaleXByTime
          this.X_Offset-=this.ZoomX
        else
          this.XTOffset-=this.ZoomT
        this.ShiftStrt:=true
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=13)
      {
        this.Transp[this.Sel]+=DeltaY
        if (this.Transp[this.Sel]&lt;1)
          this.Transp[this.Sel]:=1
        if (this.Transp[this.Sel]&gt;255)
          this.Transp[this.Sel]:=255
        this.Pen[this.Sel]:=this.Transp[this.Sel]&lt;&lt;24|this.Pen[this.Sel]&amp;0xffffff
      }
    }
    if (getkeystate(&quot;RButton&quot;,&quot;P&quot;) and this.Setting)
    {
      if (this.WhichSetting=5)
      {
        loop Ars
        {
          if (SelHBA[a_index])
          {
            this.GHide[a_index]:=!this.GHide[a_index]
            if this.GHide[a_index]
            {
              loop Ars
              {
                if !this.GHide[a_index]
                  this.Sel:=a_index
              }
            }
            break
          }
        }
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=2)
      {
        this.WhichVal:=&quot;YScale&quot;
        this.GuiValText1.Value:=&quot;Масштаб шкалы&quot;
        this.GuiValVal.Value:=this.YScale[this.Sel]
        this.GuiValText2.Value:=&quot;&quot;
        this.MenuVal()
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=3)
      {
        this.GuiValText1.Value:=&quot;Масштаб по горизонтали&quot;
        if this.ScaleXByTime
        {
          this.WhichVal:=&quot;ZoomT&quot;
          this.GuiValVal.Value:=this.ZoomT/60000
          this.GuiValText2.Value:=&quot;минут&quot;
        }
        else
        {
          this.WhichVal:=&quot;ZoomX&quot;
          this.GuiValVal.Value:=this.ZoomX
          this.GuiValText2.Value:=&quot;точек&quot;
        }
        this.MenuVal()
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=4)
      {
        this.WhichVal:=&quot;LineW&quot;
        this.GuiValText1.Value:=&quot;Толщина линии графика&quot;
        this.GuiValVal.Value:=this.LineW[this.Sel]
        this.GuiValText2.Value:=&quot;&quot;
        this.MenuVal()
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=6)
      {
        this.WhichVal:=&quot;FontSize&quot;
        this.GuiValText1.Value:=&quot;Размер шрифта&quot;
        this.GuiValVal.Value:=this.FontSize
        this.GuiValText2.Value:=&quot;&quot;
        this.MenuVal()
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=13)
      {
        this.WhichVal:=&quot;Transp&quot;
        this.GuiValText1.Value:=&quot;Видимость линии графика&quot;
        this.GuiValVal.Value:=this.Transp[this.Sel]
        this.GuiValText2.Value:=&quot;0=прозрачен 255=видим&quot;
        this.MenuVal()
        this.WhichSetting:=0
      }
    }
    if (!getkeystate(&quot;LButton&quot;,&quot;P&quot;) and !getkeystate(&quot;RButton&quot;,&quot;P&quot;))
    {
      this.Setting:=false
      this.WhichSetting:=0
    }

; Информация от маркеров
    Markers:
    max:=min:=&quot;`n&quot;
    if this.ShowMarkers
    {
      loop 2
      {
        if XMarkerCoord[a_index]
          this.DrawLine(this.RedPen,XMarkerCoord[a_index],0,XMarkerCoord[a_index],this.GPosH)
      }
      if !XMarkerCoord[1]
        MarkerText[1]:=&quot;Нажмите левой кн. мыши на график&quot;
      if !XMarkerCoord[2]
        MarkerText[2]:=&quot;Нажмите правой кн. мыши на график&quot;
      if (XMarkerCoord[1] and XMarkerCoord[2])
      {
        StDeviation:=sqrt((Deviation/ValNum))
        StDeviation:=&quot;Среднекв. отклонение = &quot; Format(&quot;{:0.&quot; this.FloatFormat &quot;f}&quot;,StDeviation)
        MarkerMean:=&quot;Среднее = &quot; Format(&quot;{:0.&quot; this.FloatFormat &quot;f}&quot;,MarkerMean)
        min:=&quot;Min = &quot; Format(&quot;{:0.&quot; this.FloatFormat &quot;f}&quot;,MarkerMin)
        max:=&quot;Max = &quot; Format(&quot;{:0.&quot; this.FloatFormat &quot;f}&quot;,MarkerMax)
      }
      String:=MarkerText[1] &quot;`n&quot; MarkerText[2] &quot;`n&quot; MarkerDText &quot;`n&quot; MarkerMean &quot;`n&quot; StDeviation &quot;`n&quot; max &quot;`n&quot; min
      Pos:=this.DrawString(String,0,this.AutoPos.Y+this.AutoPos.H,this.GPosW,,&quot;&quot;,this.Font_Colour,this.LightGreenBrush)
    }

; Подсказка о текущем увеличении
    if ((!this.ScaleXByTime and this.OldZoomX!=this.ZoomX) or this.OldZoomT!=this.ZoomT)
    {
      this.ShowZoomTip:=1000
      this.OldZoomX:=this.ZoomX
      this.OldZoomT:=this.ZoomT
    }
    if (this.ShowZoomTip&gt;0)
    {
      this.ShowZoomTip-=CycleTime
      if !this.ScaleXByTime
        this.iZoomPos:=this.DrawString(&quot;Zoom`n&quot; this.ZoomX &quot;`nточек&quot;,(this.GPosW-this.iZoomPos.W)//2,0,,,,this.Font_Colour,this.LightGreenBrush)
      else
      {
        tmp1:=this.MsToTime(this.ZoomT)
        tmp1[1]+=-1000000
        tmp2:=FormatTime(tmp1[1],&quot;dd HH:mm:ss&quot;)
        tmp2.=&quot;.&quot; tmp1[2]
        this.iZoomPos:=this.DrawString(&quot;Zoom`n&quot; tmp2,(this.GPosW-this.iZoomPos.W)//2,0,,,,this.Font_Colour,this.LightGreenBrush)
      }
    }
; Подсказка о времени отрисовки
    DllCall(&quot;QueryPerformanceCounter&quot;,&quot;Int64*&quot;,&amp;EndTime:=0)
    ElapsedTime:=round((EndTime-StartTime)/frequency*1000,1)
    this.ETPos:=this.DrawString(ElapsedTime &quot;ms&quot;,this.GPosW-this.ETPos.W,this.ZoomPos.Y+this.ETPos.H,,,,this.Font_Colour,this.Font_Back)
    if (this.HitBox(mX,mY,this.ETPos) and MouseIn)
      this.TipPos:=this.DrawString(&quot;Время отрисовки кадра&quot;,(this.GPosW-this.TipPos.W)//2,this.GPosH-this.TipPos.H,,,,this.Font_Colour,this.LightGreenBrush)

    if !this.ScaleXByTime
    {
      if (ElapsedTime&gt;1000)
      {
        if !this.TimeOut
        {
          this.TOZoomX:=this.ZoomX
          this.TimeOut:=true
        }
        else
          this.ZoomX-=round(abs(this.ZoomX)/10)
      }
      else if (this.TimeOut and this.ZoomX&lt;this.TOZoomX or ElapsedTime&lt;100)
      {
        this.TimeOut:=false
      }
    }
    else
    {
      if (ElapsedTime&gt;1000)
      {
        if !this.TimeOut
        {
          this.TOZoomT:=this.ZoomT
          this.TimeOut:=true
        }
        else
          this.ZoomT-=round(abs(this.ZoomT)/10)
      }
      else if (this.TimeOut and this.ZoomT&lt;this.TOZoomT or ElapsedTime&lt;100)
      {
        this.TimeOut:=false
      }
    }

; Обновить изображение
    if !this.GuiHiden
      this.Update()

    this.OldmX:=mX
    this.OldmY:=mY
    this.Drawing:=false
    ObjRelease(ObjPtr(this))
  }
;||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/||||
;|||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||
;||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////||
;|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|
;/////////////////////////////////////////////////////////////////////////////////////////////////

  TimeSub(Time1,Time2)
  {
    T1:=Time1[1]
    T2:=Time2[1]
    ms:=Time1[2]-Time2[2]
    if (ms&lt;0)
    {
      ms+=1000
      T2:=DateAdd(T2,1,&quot;Seconds&quot;)
    }
    Dif:=DateDiff(T1,T2,&quot;Seconds&quot;)
    return Dif*1000+ms
  }
  TimeSubMs(Time,ValueMs)
  {
    ValueMs:=round(ValueMs)
    if (ValueMs=0)
      return Time
    m:=0
    if (ValueMs&gt;0)
    {
      ms:=Time[2]-mod(ValueMs,1000)
      if (ms&lt;0)
      {
        ms+=1000
        m:=1
      }
      t:=DateAdd(Time[1],-(Floor(ValueMs/1000)+m),&quot;Seconds&quot;)
    }
    else
    {
      ms:=mod(-ValueMs,1000)+Time[2]
      if (ms&gt;999)
      {
        ms-=1000
        m:=1
      }
      t:=DateAdd(Time[1],Floor(-ValueMs/1000)+m,&quot;Seconds&quot;)
    }
    return [t,format(&quot;{:03}&quot;,ms)]
  }
  TimeAddMs(Time,ValueMs)
  {
    ValueMs:=round(ValueMs)
    T:=DateAdd(Time[1],ValueMs//1000,&quot;Seconds&quot;)
    ms:=Time[2]+mod(ValueMs,1000)
    if (ms&gt;999)
    {
      ms-=1000
      T:=DateAdd(T,1,&quot;Seconds&quot;)
    }
    return [T,format(&quot;{:03}&quot;,ms)]
  }
  TimeToMs(Time)
  {
    HH:=FormatTime(Time,&quot;HH&quot;)
    MM:=FormatTime(Time,&quot;mm&quot;)
    SS:=FormatTime(Time,&quot;ss&quot;)
    return HH*3600000+MM*60000+SS*1000
  }
  MsToTime(Ms)
  {
    T:=DateAdd(1601,Ms//1000,&quot;Seconds&quot;)
    Ms:=format(&quot;{:03}&quot;,mod(Ms,1000))
    return [T,Ms]
  }

  TTOk(*)
  {
    ToTime:=this.hTT.Value
    tmp1:=DateDiff(A_Now,ToTime,&quot;Seconds&quot;)
    this.XTOffset:=tmp1*1000-round((this.GPosW/2)/this.PerioT)
    this.Pause:=true
    this.ShiftStrt:=true
    this.GuiGoToT.Hide()
  }
  TTCancel(*)
  {
    this.GuiGoToT.Hide()
  }
  TXOk(*)
  {
    this.X_Offset:=this.ArrSize[this.Sel]-this.hTX.Value
    this.Pause:=true
    this.GuiGoToX.Hide()
  }
  TXCancel(*)
  {
    this.GuiGoToX.Hide()
  }
  NameOk(*)
  {
    this.ArrName[this.Sel]:=this.GuiNameVal.Value
    this.GuiName.Hide()
  }
  NameCancel(*)
  {
    this.GuiName.Hide()
  }
  ValAcc(*)
  {
    if (this.WhichVal=&quot;&quot;)
      return
    Whichval:=this.Whichval
    if (Whichval=&quot;YScale&quot;)
    {
      this.%Whichval%[this.Sel]:=this.GuiValVal.Value
      this.AutoScale[this.Sel]:=false
      if this.OScale
        loop this.AutoScale.Length
          this.AutoScale[a_index]:=this.AutoScale[this.Sel]
    }
    else if (Whichval=&quot;ZoomX&quot;)
    {
      this.%Whichval%:=round(this.GuiValVal.Value)
    }
    else if (Whichval=&quot;ZoomT&quot;)
    {
      this.%Whichval%:=round(this.GuiValVal.Value*60000)
    }
    else if (Whichval=&quot;FontSize&quot;)
    {
      this.%Whichval%:=this.GuiValVal.Value
      if (this.FontSize!=this.prevSize)
        this.SetFont(,this.FontSize)
    }
    else if (Whichval=&quot;Transp&quot;)
    {
      this.%Whichval%[this.Sel]:=round(this.GuiValVal.Value)&amp;0xff
      this.Pen[this.Sel]:=this.Transp[this.Sel]&lt;&lt;24|this.Pen[this.Sel]&amp;0xffffff
    }
    else if (Whichval=&quot;UpdatePeriod&quot;)
    {
      this.UpdatePeriod:=this.GuiValVal.Value
      settimer this.UpdateTimer,this.UpdatePeriod
    }
    else if (Whichval!=&quot;&quot;)
    {
      if (isObject(this.%Whichval%))
        this.%Whichval%[this.Sel]:=this.GuiValVal.Value
      else
        this.%Whichval%:=this.GuiValVal.Value
    }
  }
  ValOk(*)
  {
    this.ValAcc()
    this.WhichVal:=&quot;&quot;
    this.GuiVal.Hide()
  }
  ValCancel(*)
  {
    this.WhichVal:=&quot;&quot;
    this.GuiVal.Hide()
  }
  HelpClose(*)
  {
    this.GuiHelp.Hide()
  }

  MenuVal(*)
  {
    this.GuiVal.Show()
  }
  MenuFlFt(*)
  {
    this.WhichVal:=&quot;FloatFormat&quot;
    this.GuiValText1.Value:=&quot;Количество знаков после запятой&quot;
    this.GuiValVal.Value:=this.FloatFormat
    this.GuiValText2.Value:=&quot;&quot;
    this.MenuVal()
  }
  MenuUpdPrd(*)
  {
    this.WhichVal:=&quot;UpdatePeriod&quot;
    this.GuiValText1.Value:=&quot;Период обновления окна графика&quot;
    this.GuiValVal.Value:=this.UpdatePeriod
    this.GuiValText2.Value:=&quot;ms&quot;
    this.MenuVal()
  }
  MenuName(*)
  {
    this.GuiNameVal.Value:=this.ArrName[this.Sel]
    this.GuiName.Show()
  }
  MenuGColor(*)
  {
    tmp1:=ChooseColor(this.Pen[this.Sel],this.GuiGraph.Hwnd)
    if (tmp1!=&quot;&quot;)
    {
      tmp2:=this.RGBToHSV(tmp1)
      if ((tmp2&gt;&gt;8)&amp;0xff=0)
      {
        tmp3:=(tmp2&amp;0xff)
        if (tmp3&lt;127)
          tmp3:=127
        this.Hue[this.Sel]:=tmp3&lt;&lt;8
      }
      else
        this.Hue[this.Sel]:=tmp2&gt;&gt;16
      this.Pen[this.Sel]:=0xff000000 | tmp1
    }
  }
  MenuHelp(*)
  {
    this.GuiHelp.Show()
  }
  MenuGoTo(*)
  {
    if this.ScaleXByTime
    {
      this.GuiGoToT.Show()
    }
    else
    {
      this.GuiGoToX.Show()
    }
  }
  MenuClose(*)
  {
    this.Hide()
  }
  MenuPause(*)
  {
    this.Pause:=!this.Pause
  }
  MenuForward(*)
  {
    this.Pause:=false
    this.XTOffset:=0
    this.X_Offset:=0
    this.ShiftStrt:=true
  }
  MenuBackward(*)
  {
    this.Pause:=true
    this.X_Offset:=this.ArrSize[this.Sel]-this.ZoomX//2
    if this.ScaleXByTime
    {
      if this.TimeStampExist[this.Sel]
        tmp1:=DateDiff(A_Now,this.pData[1][2],&quot;Seconds&quot;)
      else
        tmp1:=DateDiff(A_Now,this.StampT[this.Sel][1][1],&quot;Seconds&quot;)
      this.XTOffset:=tmp1*1000-this.ZoomT//2
    }
    this.ShiftStrt:=true
  }
  MenuMarkers(*)
  {
    Add:=this.Pos.H*8
    if this.ShowMarkers:=!this.ShowMarkers
    {
      Size:=100+Add
      this.GuiGraph.Opt(&quot;+MinSize300x&quot; Size)
      this.GuiGraph.GetPos(,,,&amp;BPosH)
      this.GuiGraph.Move(,,,BPosH+Add)
      this.MarkerOffset:=Add
    }
    else
    {
      this.GuiGraph.Opt(&quot;+MinSize300x100&quot;)
      this.GuiGraph.GetPos(,,,&amp;BPosH)
      this.GuiGraph.Move(,,,BPosH-Add)
      this.MarkerOffset:=0
    }
  }
  MenuAutoScale(*)
  {
    if (this.Lock_hi_lim[this.Sel] or this.Lock_lo_lim[this.Sel])
      this.AutoScale[this.Sel]:=false
    if this.AutoScale[this.Sel]:=!this.AutoScale[this.Sel]
    {
      this.Lock_hi_lim[this.Sel]:=false
      this.Lock_lo_lim[this.Sel]:=false
      this.GraphMenuSettings.check(this.mi.MenuAutoScale)
    }
    else
      this.GraphMenuSettings.uncheck(this.mi.MenuAutoScale)
  }
  MenuAlwaysOnTop(*)
  {
    if this.AlwaysOnTop:=!this.AlwaysOnTop
    {
      this.GuiGraph.Opt(&quot;+alwaysontop&quot;)
      this.GraphMenuSettings.check(this.mi.MenuAlwaysOnTop)
    }
    else
    {
      this.GuiGraph.Opt(&quot;-alwaysontop&quot;)
      this.GraphMenuSettings.uncheck(this.mi.MenuAlwaysOnTop)
    }
  }
  MenuSmooth(*)
  {
    if this.Smooth[this.Sel]:=!this.Smooth[this.Sel]
      this.GraphMenuSettings.check(this.mi.MenuSmooth)
    else
      this.GraphMenuSettings.uncheck(this.mi.MenuSmooth)
  }
  MenuScaleXByTime(*)
  {
    this.ScaleXByTime:=true
    this.GraphMenuSettings.check(this.mi.MenuScaleXByTime)
    this.GraphMenuSettings.uncheck(this.mi.MenuScaleXByDots)
  }
  MenuScaleXByDots(*)
  {
    this.ScaleXByTime:=false
    this.GraphMenuSettings.uncheck(this.mi.MenuScaleXByTime)
    this.GraphMenuSettings.check(this.mi.MenuScaleXByDots)
  }
  MenuResetFont(*)
  {
    this.FontSize:=12
    this.SetFont(,this.FontSize)
  }
  MenuSave(*)
  {
    SaveFileName:=FileSelect(&quot;S 16&quot;,,&quot;Сохранить данные графика&quot;,&quot;(*.garr)&quot;)
    if (SaveFileName!=&quot;&quot;)
    {
      if !InStr(SaveFileName,&quot;.garr&quot;)
        SaveFileName.=&quot;.garr&quot;
      o:=this.Data.Clone()
      o.Push(this.CreateTime)
      o.Push(this.ArrName)
      tooltip &quot;Сохраняется...&quot;
      ObjDump(SaveFileName,o)
      tooltip
      msgbox &quot;Сохранено.&quot;
    }
  }
  MenuLoad(*)
  {
    SaveFileName:=FileSelect(3,,&quot;Загрузить данные графика для просмотра&quot;,&quot;(*.garr)&quot;)
    if (SaveFileName!=&quot;&quot;)
      this.LoadFile(SaveFileName)
  }
  DropFiles(GuiObj,GuiCtrlObj,FileArray,X,Y)
  {
    if instr(FileArray[1],&quot;.garr&quot;)
      this.LoadFile(FileArray[1])
  }
  LoadFile(File)
  {
    o:=ObjLoad(File)
    this.SaveArrName:=this.ArrName.Clone()
    this.ArrName:=[]
    for v in o[o.Length]
    {
      if !isobject(v) and !isNumber(V)
      {
        this.ArrName:=o.Pop()
        this.CreateTime:=o.Pop()
        break
      }
    }
    this.NewData:=o
    this.MenuBackward()
    this.Pause:=true
    this.Loaded:=1
  }
  UpdateMenu()
  {
    if this.AutoScale[this.Sel]
      this.GraphMenuSettings.check(this.mi.MenuAutoScale)
    else
      this.GraphMenuSettings.uncheck(this.mi.MenuAutoScale)
    if this.Smooth[this.Sel]
      this.GraphMenuSettings.check(this.mi.MenuSmooth)
    else
      this.GraphMenuSettings.uncheck(this.mi.MenuSmooth)
  }

  AHSVToARGB(a:=255,h:=0,s:=0,v:=0)
  {
    H:=h/255
    S:=s/255
    V:=v/255
    i:=Floor(H*6)
    f:=H*6-i
    p:=V*(1-S)
    q:=V*(1-f*S)
    t:=V*(1-(1-f)*S)
    sw:=mod(i,6)
    if (sw=0)
      r:=V, g:=t, b:=p
    if (sw=1)
      r:=q, g:=V, b:=p
    if (sw=2)
      r:=p, g:=V, b:=t
    if (sw=3)
      r:=p, g:=q, b:=V
    if (sw=4)
      r:=t, g:=p, b:=V
    if (sw=5)
      r:=V, g:=p, b:=q
    r:=(Integer(r*255))&amp;0xff
    g:=(Integer(g*255))&amp;0xff
    b:=(Integer(b*255))&amp;0xff
    return (a&lt;&lt;24 | r&lt;&lt;16 | g&lt;&lt;8 | b)
  }
  RGBToHSV(RGB)
  {
    r:=(RGB&gt;&gt;16 &amp; 0xff)/255
    g:=(RGB&gt;&gt;8 &amp; 0xff)/255
    b:=(RGB &amp; 0xff)/255
    min:=(r&lt;g)?((r&lt;b)?r:b):((g&lt;b)?g:b)
    val:=(r&gt;g)?((r&gt;b)?r:b):((g&gt;b)?g:b)
    delta:=val-min
    if (delta=0) ;pure blackness
      return (RGB &amp; 0xff)
    if (r=val) ;between yellow and magenta
      hue:=(g-b)/delta
    else if (g=val) ;between cyan and yellow
      hue:=2+(b-r)/delta
    else ;between magenta and cyan
      hue:=4+(r-g)/delta
    if (hue&lt;=0)
      hue+=6
    hue:=round(hue/6*255)
    sat:=round((Delta/val)*255)
    val:=round(val*255)
    return (hue&lt;&lt;16 | sat&lt;&lt;8 | val)
  }
  Show(x:=&quot;&quot;,y:=&quot;&quot;,w:=&quot;&quot;,h:=&quot;&quot;,NewName:=&quot;&quot;,NewUP:=&quot;&quot;)
  {
    if NewName
      this.Name:=NewName
    if IsInteger(NewUP)
      this.UpdatePeriod:=NewUP
    this.GuiHiden:=false
    this.GuiGraph.Title:=this.Name
    this.GuiGraph.Show((x=&quot;&quot;?&quot;&quot;:&quot;x&quot; x &quot; &quot;) . (y=&quot;&quot;?&quot;&quot;:&quot;y&quot; y &quot; &quot;) . (w=&quot;&quot;?&quot;&quot;:&quot;w&quot; w &quot; &quot;) . (h=&quot;&quot;?&quot;&quot;:&quot;h&quot; h &quot; &quot;))
    if !this.SetParentOnce
    {
      this.SetParentOnce:=true
      DllCall(&quot;SetParent&quot;,&quot;Ptr&quot;,this.GuiGDIP.hwnd,&quot;Ptr&quot;,this.GuiGraph.hwnd)
      WinMove(0,0,,,&quot;ahk_id &quot; this.GuiGDIP.hwnd)
      this.GuiGraph.Show()
    }
    settimer this.UpdateTimer,this.UpdatePeriod
  }

  Hide(*)
  {
    this.GuiHiden:=true
    settimer this.UpdateTimer,0
    this.Update(0)
    this.GuiGraph.Hide()
    if this.IsChild
      this.Parent.DeleteChild(this.IsChild)
  }
  DeleteChild(Num)
  {
    loop this.Child.Length
    {
      if (this.Child[a_index].IsChild=Num)
      {
        ObjAddRef(ObjPtr(this))
        this.Child[a_index]:=&quot;&quot;
        this.Child.RemoveAt(a_index)
        break
      }
    }
  }

  Create()
  {
    this.hDC:=DllCall(&quot;CreateCompatibleDC&quot;,&quot;UPtr&quot;,0)
    bi:=Buffer(40,0)
    NumPut(&quot;UInt&quot;,40,bi,0)
    NumPut(&quot;UInt&quot;,A_ScreenWidth,bi,4)
    NumPut(&quot;UInt&quot;,A_ScreenHeight,bi,8)
    NumPut(&quot;UShort&quot;,1,bi,12)
    NumPut(&quot;UShort&quot;,32,bi,14)
    NumPut(&quot;UInt&quot;,0,bi,16)
    this.hBitmap:=DllCall(&quot;CreateDIBSection&quot;
                    ,&quot;Ptr&quot;,this.hDC
                    ,&quot;Ptr&quot;,bi.Ptr
                    ,&quot;uint&quot;,0
                    ,&quot;UPtr*&quot;,0
                    ,&quot;Ptr&quot;,0
                    ,&quot;uint&quot;,0,&quot;Ptr&quot;)
    this.obm:=DllCall(&quot;SelectObject&quot;,&quot;Ptr&quot;,this.hDC,&quot;Ptr&quot;,this.hBitmap)
    DllCall(Graph.hGdipCreateFromHDC,&quot;Ptr&quot;,this.hDC,&quot;UPtr*&quot;,&amp;pGraphics:=0)
    this.pGraphics:=pGraphics
    DllCall(Graph.hGdipSetSmoothingMode,&quot;UPtr&quot;,this.pGraphics,&quot;Int&quot;,4)
  }

  Update(Alpha:=255)
  {
    WinGetClientPos(,,&amp;GPosW,&amp;GPosH,this.GuiGraph.hwnd)
    this.GPosW:=GPosW
    this.GPosH:=GPosH-this.StampOffset-this.MarkerOffset-this.MenuOffset
    this.ZeroPosU:=this.GPosH-this.ZeroPos
    if (this.ZeroPosU&lt;0 and this.GPosH_!=GPosH)
    {
      this.ZeroPos:=GPosH//2
      this.ZeroPosU:=this.GPosH-this.ZeroPos
    }
    this.GPosH_:=GPosH
    pt:=Buffer(8,0)
    DllCall(&quot;UpdateLayeredWindow&quot;
            ,&quot;Ptr&quot;,this.GuiGDIP.Hwnd
            ,&quot;Ptr&quot;,0
            ,&quot;Ptr&quot;,pt.Ptr
            ,&quot;UInt*&quot;,GPosW|GPosH&lt;&lt;32
            ,&quot;Ptr&quot;,this.hDC
            ,&quot;int64*&quot;,0
            ,&quot;uint&quot;,0
            ,&quot;UInt*&quot;,Alpha&lt;&lt;16|1&lt;&lt;24
            ,&quot;uint&quot;,2)
  }

  CreateSolidBrush(ARGB:=0xff000000)
  {
    DllCall(Graph.hGdipCreateSolidFill,&quot;UInt&quot;,ARGB,&quot;UPtr*&quot;,&amp;pBrush:=0)
    return pBrush
  }
  DeleteBrush(pBrush)
  {
    DllCall(Graph.hGdipDeleteBrush,&quot;UPtr&quot;,pBrush)
    return 0
  }
  CreatePen(ARGB:=0xff000000,w:=1)
  {
    DllCall(Graph.hGdipCreatePen1,&quot;UInt&quot;,ARGB,&quot;float&quot;,w,&quot;int&quot;,2,&quot;UPtr*&quot;,&amp;pPen:=0)
    return pPen
  }
  DeletePen(pPen)
  {
    DllCall(Graph.hGdipDeletePen,&quot;UPtr&quot;,pPen)
    return 0
  }

  FillRectangle(ARGB,x,y,w,h)
  {
    if (!this.pBrush or this.prevBrushColor!=ARGB)
    {
      if this.pBrush
        this.pBrush:=this.DeleteBrush(this.pBrush)
      this.pBrush:=this.CreateSolidBrush(ARGB)
    }
    this.prevBrushColor:=ARGB
    DllCall(Graph.hGdipFillRectangle
            ,&quot;Ptr&quot;,this.pGraphics
            ,&quot;Ptr&quot;,this.pBrush
            ,&quot;float&quot;,x
            ,&quot;float&quot;,y
            ,&quot;float&quot;,w
            ,&quot;float&quot;,h)
    return {X:x,Y:y,W:w,H:h}
  }
  DrawLine(ARGB,x1,y1,x2,y2,LineWidth:=1)
  {
    if (!this.pPen or this.prevPenColor!=ARGB or this.OldLineWidth!=LineWidth)
    {
      if this.pPen
        this.pPen:=this.DeletePen(this.pPen)
      this.pPen:=this.CreatePen(ARGB,LineWidth)
    }
    this.OldLineWidth:=LineWidth
    this.prevPenColor:=ARGB
    DllCall(Graph.hGdipDrawLine
            ,&quot;Ptr&quot;,this.pGraphics
            ,&quot;Ptr&quot;,this.pPen
            ,&quot;float&quot;,x1
            ,&quot;float&quot;,y1
            ,&quot;float&quot;,x2
            ,&quot;float&quot;,y2)
    return {X1:x1,Y1:y1,X2:x2,Y2:y2}
  }
  DrawLines(ARGB,Points,LineWidth:=1)
  {
    tmp1:=Points.Length
    if !tmp1
      return -1
    PointF:=Buffer(8*tmp1)
    Loop tmp1
    {
      NumPut(&quot;float&quot;,Points[a_index][1],PointF,8*(A_Index-1))
      NumPut(&quot;float&quot;,Points[a_index][2],PointF,(8*(A_Index-1))+4)
    }

    if (!this.pPen or this.prevPenColor!=ARGB or this.OldLineWidth!=LineWidth)
    {
      if this.pPen
        this.pPen:=this.DeletePen(this.pPen)
      this.pPen:=this.CreatePen(ARGB,LineWidth)
    }
    this.OldLineWidth:=LineWidth
    this.prevPenColor:=ARGB
    return DllCall(Graph.hGdipDrawLines
                  ,&quot;Ptr&quot;,this.pGraphics
                  ,&quot;Ptr&quot;,this.pPen
                  ,&quot;Ptr&quot;,PointF.Ptr
                  ,&quot;int&quot;,tmp1)
  }
  DrawEllipse(ARGB,x,y,w,h,LineWidth:=1)
  {
    if (!this.pPen or this.prevPenColor!=ARGB or this.OldLineWidth!=LineWidth)
    {
      if this.pPen
        this.pPen:=this.DeletePen(this.pPen)
      this.pPen:=this.CreatePen(ARGB,LineWidth)
    }
    this.OldLineWidth:=LineWidth
    this.prevPenColor:=ARGB
    DllCall(Graph.hGdipDrawEllipse
              ,&quot;Ptr&quot;,this.pGraphics
              ,&quot;Ptr&quot;,this.pPen
              ,&quot;float&quot;,x
              ,&quot;float&quot;,y
              ,&quot;float&quot;,w
              ,&quot;float&quot;,h)
    return {X:x,Y:y,W:w,H:h}
  }
  DrawRectangle(ARGB,x,y,w,h,LineWidth:=1)
  {
    if (!this.pPen or this.prevPenColor!=ARGB or this.OldLineWidth!=LineWidth)
    {
      if this.pPen
        this.pPen:=this.DeletePen(this.pPen)
      this.pPen:=this.CreatePen(ARGB,LineWidth)
    }
    this.OldLineWidth:=LineWidth
    this.prevPenColor:=ARGB
    DllCall(Graph.hGdipDrawRectangle
                ,&quot;Ptr&quot;,this.pGraphics
                ,&quot;Ptr&quot;,this.pPen
                ,&quot;float&quot;,x
                ,&quot;float&quot;,y
                ,&quot;float&quot;,w
                ,&quot;float&quot;,h)
    return {X:x,Y:y,W:w,H:h}
  }

  SetFont(Font:=&quot;&quot;,Size:=&quot;&quot;,fStyle:=&quot;&quot;,NoWrap:=&quot;&quot;,RenderingHint:=&quot;&quot;,Vertical:=&quot;&quot;)
  {
    Style:=0,Styles:=&quot;Regular|Bold|Italic|BoldItalic|Underline|Strikeout&quot;
    Loop Parse Styles,&quot;|&quot;
    {
      if RegExMatch(fStyle, &quot;\b&quot; A_loopField)
        Style|=(A_LoopField!=&quot;StrikeOut&quot;)?(A_Index-1):8
    }

    if (!this.hFormat
   or (this.prevNoWrap!=NoWrap and NoWrap!=&quot;&quot;)
   or (this.prevVertical!=Vertical and Vertical!=&quot;&quot;))
    {
      if this.hFormat
      {
        DllCall(Graph.hGdipDeleteStringFormat,&quot;UPtr&quot;,this.hFormat)
        this.hFormat:=&quot;&quot;
      }
      Flags:=0x4000
      Flags:=NoWrap?(Flags|0x1000):Flags
      Flags:=Vertical?(Flags|0x2):Flags
      DllCall(Graph.hGdipCreateStringFormat,&quot;int&quot;,Flags,&quot;int&quot;,0,&quot;UPtr*&quot;,&amp;hFormat:=0)
      this.hFormat:=hFormat
      this.prevNoWrap:=NoWrap
      this.prevVertical:=Vertical
    }

    if (!this.hFamily or (this.prevFont!=Font and Font!=&quot;&quot;))
    {
      if this.hFamily
      {
        if this.hFont
        {
          DllCall(Graph.hGdipDeleteFont,&quot;UPtr&quot;,this.hFont)
          this.hFont:=&quot;&quot;
        }
        DllCall(Graph.hGdipDeleteFontFamily,&quot;UPtr&quot;,this.hFamily)
        this.hFamily:=&quot;&quot;
      }
      Font:=Font=&quot;&quot; ? &quot;Arial&quot; : Font
;      if !A_IsUnicode
;      {
;        nSize:=DllCall(&quot;MultiByteToWideChar&quot;,&quot;uint&quot;,0,&quot;uint&quot;,0,&quot;Ptr&quot;,&amp;Font,&quot;int&quot;,-1,&quot;uint&quot;,0,&quot;int&quot;,0)
;        VarSetCapacity(wFont,nSize*2)
;        DllCall(&quot;MultiByteToWideChar&quot;,&quot;uint&quot;,0,&quot;uint&quot;,0,&quot;Ptr&quot;,&amp;Font,&quot;int&quot;,-1,&quot;Ptr&quot;,&amp;wFont,&quot;int&quot;,nSize)
;      }
;      DllCall(Graph.hGdipCreateFontFamilyFromName
;              ,&quot;Ptr&quot;,A_IsUnicode ? &amp;Font : &amp;wFont
;              ,&quot;uint&quot;,0
;              ,&quot;UPtr*&quot;,hFamily)
      DllCall(Graph.hGdipCreateFontFamilyFromName
              ,&quot;Ptr&quot;,StrPtr(String(Font))
              ,&quot;uint&quot;,0
              ,&quot;UPtr*&quot;,&amp;hFamily:=0)
      this.hFamily:=hFamily
      this.prevFont:=Font
    }

    if (!this.hFont or (this.prevSize!=Size and Size!=&quot;&quot;) or (this.prevfStyle!=fStyle and fStyle!=&quot;&quot;))
    {
      if this.hFont
      {
        DllCall(Graph.hGdipDeleteFont,&quot;UPtr&quot;,this.hFont)
        this.hFont:=&quot;&quot;
      }
      DllCall(Graph.hGdipCreateFont
    ,&quot;UPtr&quot;,this.hFamily
    ,&quot;float&quot;,(Size ? Size : 12)
    ,&quot;int&quot;,fStyle=&quot;&quot; ? 0 : Style
    ,&quot;int&quot;,0
    ,&quot;UPtr*&quot;,&amp;hFont:=0)
      this.hFont:=hFont
      this.prevSize:=Size
      this.prevfStyle:=fStyle
    }

    RenderingHint:=(RenderingHint=&quot;&quot;) ? ((this.prevRenderingHint=&quot;&quot;) ? 0 : this.prevRenderingHint) : RenderingHint
    this.prevRenderingHint:=RenderingHint
    ; SystemDefault = 0
    ; SingleBitPerPixelGridFit = 1
    ; SingleBitPerPixel = 2
    ; AntiAliasGridFit = 3
    ; AntiAlias = 4
    DllCall(Graph.hGdipSetTextRenderingHint,&quot;UPtr&quot;,this.pGraphics,&quot;int&quot;,RenderingHint)
  }

  DrawString(Text,xpos:=0,ypos:=0,Width:=0,Height:=0,Options:=&quot;Center&quot;,Colour:=0xff000000,BackgroundColour:=0)
  {
    if !(this.hFamily and this.hFont and this.hFormat)
      this.SetFont()

    Align:=0
    Loop Parse &quot;Near|Left|Centre|Center|Far|Right&quot;,&quot;|&quot;
    {
      if RegExMatch(Options, &quot;\b&quot; A_loopField)
        Align|=Floor(A_Index/2.1)      ; 0|0|1|1|2|2
    }
    DllCall(Graph.hGdipSetStringFormatAlign,&quot;UPtr&quot;,this.hFormat,&quot;int&quot;,Align)

    if (!this.FontpBrush or Colour!=this.prevColour)
    {
      if this.FontpBrush
        this.FontpBrush:=this.DeleteBrush(this.FontpBrush)
      this.FontpBrush:=this.CreateSolidBrush(Colour)
      this.prevColour:=Colour
    }

    RC:=this.CreateRectF(xpos,ypos,Width,Height)
    ReturnRC:=this.MeasureString(Text,RC)
    if (!Width or !Height)
    {
      Width:=Width ? Width : ReturnRC.W
      Height:=Height ? Height : ReturnRC.H
      RC:=this.CreateRectF(xpos,ypos,Width,Height)
      ReturnRC:=this.MeasureString(Text,RC)
    }
    if BackgroundColour
      this.FillRectangle(BackgroundColour,xpos,ypos,Width,Height)
    RegExMatch(Options,&quot;i)Top|Up|Bottom|Down|vCentre|vCenter&quot;,&amp;vPos)
    if vPos
    {
      vPos:=SubStr(Options,vPos.Pos,vPos.Len)
      if (vPos=&quot;vCentre&quot; or vPos=&quot;vCenter&quot;)
        ypos+=(Height-ReturnRC.H)//2
      else if (vPos=&quot;Top&quot; or vPos=&quot;Up&quot;)
        ypos:=ReturnRC.Y
      else if (vPos=&quot;Bottom&quot; or vPos=&quot;Down&quot;)
        ypos+=Height-ReturnRC.H

      RC:=this.CreateRectF(xpos,ypos,Width,ReturnRC.H)
      ReturnRC:=this.MeasureString(Text,RC)
    }

;    sString:=Text
;    if (!A_IsUnicode)
;    {
;      nSize:=DllCall(&quot;MultiByteToWideChar&quot;,&quot;uint&quot;,0,&quot;uint&quot;,0,&quot;Ptr&quot;,&amp;sString,&quot;int&quot;,-1,&quot;Ptr&quot;,0,&quot;int&quot;,0)
;      VarSetCapacity(wString,nSize*2)
;      DllCall(&quot;MultiByteToWideChar&quot;,&quot;uint&quot;,0,&quot;uint&quot;,0,&quot;Ptr&quot;,&amp;sString,&quot;int&quot;,-1,&quot;Ptr&quot;,&amp;wString,&quot;int&quot;,nSize)
;    }
;    DllCall(Graph.hGdipDrawString
;            ,&quot;Ptr&quot;,this.pGraphics
;            ,&quot;Ptr&quot;,A_IsUnicode ? &amp;sString : &amp;wString
;            ,&quot;int&quot;,-1
;            ,&quot;Ptr&quot;,this.hFont
;            ,&quot;Ptr&quot;,&amp;RC
;            ,&quot;Ptr&quot;,this.hFormat
;            ,&quot;Ptr&quot;,this.FontpBrush)
    DllCall(Graph.hGdipDrawString
            ,&quot;Ptr&quot;,this.pGraphics
            ,&quot;Ptr&quot;,StrPtr(String(Text))
            ,&quot;int&quot;,-1
            ,&quot;Ptr&quot;,this.hFont
            ,&quot;Ptr&quot;,RC.Ptr
            ,&quot;Ptr&quot;,this.hFormat
            ,&quot;Ptr&quot;,this.FontpBrush)
    return ReturnRC
  }
  CreateRectF(x,y,w,h)
  {
    RectF:=Buffer(16)
    NumPut(&quot;float&quot;,x,RectF,0)
    NumPut(&quot;float&quot;,y,RectF,4)
    NumPut(&quot;float&quot;,w,RectF,8)
    NumPut(&quot;float&quot;,h,RectF,12)
    return RectF
  }

  MeasureString(sString,RectF)
  {
    RC:=Buffer(16)
;    if !A_IsUnicode
;    {
;      nSize:=DllCall(&quot;MultiByteToWideChar&quot;,&quot;uint&quot;,0,&quot;uint&quot;,0,&quot;Ptr&quot;,&amp;sString,&quot;int&quot;,-1,&quot;uint&quot;,0,&quot;int&quot;,0)
;      VarSetCapacity(wString,nSize*2)
;      DllCall(&quot;MultiByteToWideChar&quot;,&quot;uint&quot;,0,&quot;uint&quot;,0,&quot;Ptr&quot;,&amp;sString,&quot;int&quot;,-1,&quot;Ptr&quot;,&amp;wString,&quot;int&quot;,nSize)
;    }
;    if !DllCall(Graph.hGdipMeasureString
;            ,&quot;Ptr&quot;,this.pGraphics
;            ,&quot;Ptr&quot;,A_IsUnicode ? &amp;sString : &amp;wString
;            ,&quot;int&quot;,-1
;            ,&quot;Ptr&quot;,this.hFont
;            ,&quot;Ptr&quot;,&amp;RectF
;            ,&quot;Ptr&quot;,this.hFormat
;            ,&quot;Ptr&quot;,&amp;RC
;            ,&quot;uint*&quot;,Chars
;            ,&quot;uint*&quot;,Lines)
    if !DllCall(Graph.hGdipMeasureString
            ,&quot;Ptr&quot;,this.pGraphics
            ,&quot;Ptr&quot;,StrPtr(String(sString))
            ,&quot;int&quot;,-1
            ,&quot;Ptr&quot;,this.hFont
            ,&quot;Ptr&quot;,RectF.Ptr
            ,&quot;Ptr&quot;,this.hFormat
            ,&quot;Ptr&quot;,RC.Ptr
            ,&quot;uint*&quot;,&amp;Chars:=0
            ,&quot;uint*&quot;,&amp;Lines:=0)
    return	{X:Ceil(NumGet(RC,0,&quot;float&quot;))
    ,Y:Ceil(NumGet(RC,4,&quot;float&quot;))
    ,W:Ceil(NumGet(RC,8,&quot;float&quot;))
    ,H:Ceil(NumGet(RC,12,&quot;float&quot;))
    ,Chars:Chars
    ,Lines:Lines}
  }
}

#HotIf WinActive(&quot;ahk_class AutoHotkeyGUI&quot;,Graph.WinText)
WheelUp::
{
  MouseGetPos(,,&amp;mW)
  Graph.WheelUp:=true
  Graph.HotKeymW:=mW
}
WheelDown::
{
  MouseGetPos(,,&amp;mW)
  Graph.WheelDown:=true
  Graph.HotKeymW:=mW
}
#HotIf



/*  Windows Color Picker Plus
    https-//autohotkey.com/board/topic/91229-windows-color-picker-plus/
    Edited by me lemasato-
        BGR2RGB()-
        Use Format() to convert the value to hex instead of SetFormat

    Function- ChooseColor([pRGB, hOwner, DlgX, DlgY, Palette])
        Displays a standard Windows dialog for choosing colors.
    Parameters-
        pRGB - The initial color to display in the dialog in RGB format.
              The default setting is Black.
        hOwner - The Window ID of the dialog&#039;s owner, if it has one. Defaults to
                0, i.e. no owner. If specified DlgX and DlgY are ignored.
        DlgX, DlgY - The X and Y coordinates of the upper left corner of the
                    dialog. Both default to 0.
        Palette - An array of up to 16 RGB color values. These become the
                  initial custom colors in the dialog.
    Remarks-
        The custom colors in the dialog are remembered between calls.

        If the user selects OK, the Palette array (if it exists) will be loaded
        with the custom colors from the dialog.
    Returns-
        If the user selects OK, the selected color is returned in RGB format
        and ErrorLevel is set to 0. Otherwise, the original pRGB value is
        returned and ErrorLevel is set to 1.
*/

ChooseColor(pRGB:=0,hOwner:=0,DlgX:=0,DlgY:=0)
{
  static CustColors:=Buffer(64,0)    ; Custom colors are remembered between calls
  static SizeOfCustColors:=CustColors.size
  static ChooseColor:=Buffer(9*A_PtrSize,0)
  static StructSize:=ChooseColor.size
  static Palette:=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

  CustData:=(DlgX&lt;&lt;16)|DlgY    ; Store X in high word, Y in the low word

;___Load user&#039;s custom colors
  loop 16
    NumPut(&quot;UInt&quot;,BGR2RGB(Palette[a_index]),CustColors,(A_Index-1)*4)

;___Set up a ChooseColor structure as described in the MSDN
  NumPut(&quot;UInt&quot;,StructSize,ChooseColor,0)
  NumPut(&quot;UPtr&quot;,hOwner,ChooseColor,A_PtrSize)
  NumPut(&quot;UInt&quot;,BGR2RGB(pRGB),ChooseColor,3*A_PtrSize)
  NumPut(&quot;UPtr&quot;,CustColors.Ptr,ChooseColor,4*A_PtrSize)
  NumPut(&quot;UInt&quot;,0x113,ChooseColor,5*A_PtrSize)
  NumPut(&quot;UInt&quot;,CustData,ChooseColor,6*A_PtrSize)
  NumPut(&quot;UPtr&quot;,CallbackCreate(ColorWindowProc),ChooseColor,7*A_PtrSize)

;___Call the function
  ErrorLevel:=!DllCall(&quot;comdlg32\ChooseColor&quot;,&quot;UPtr&quot;,ChooseColor.Ptr,&quot;UInt&quot;)

;___Save the changes made to the custom colors
  if !ErrorLevel
    Loop 16
      Palette[A_Index]:=BGR2RGB(NumGet(CustColors,(A_Index-1)*4,&quot;UInt&quot;))
  if !ErrorLevel ; modified by Alectric
    return BGR2RGB(NumGet(ChooseColor,3*A_PtrSize,&quot;UINT&quot;))
}

/*!
    Function- ColorWindowProc(hwnd, msg, wParam, lParam)
        Callback function used to modify the Color dialog before it is displayed
    Parameters-
        hwnd - Handle to the Color dialog window.
        msg - The message sent to the window.
        wParam - The handle to the control that has the keyboard focus.
        lParam - A pointer to the ChooseColor structure associated with the
                Color dialog.
    Remarks-
        This is intended to be a private function, called only by ChooseColor.
        In response to a WM_INITDIALOG message, this function can be used to
        modify the Color dialog before it is displayed. Currently it just moves
        the window to a new X, Y location.
    Returns-
        If the hook procedure returns zero, the default dialog box procedure
        also processes the message. Otherwise, the default dialog box procedure
        ignores the message.
*/
ColorWindowProc(hwnd,msg,wParam,lParam)
{
  static WM_INITDIALOG:=0x0110

  if (msg!=WM_INITDIALOG)
    return 0

  hOwner:=NumGet(lParam,A_PtrSize,&quot;UPtr&quot;)
  if hOwner
    return 0

  DetectSetting:=A_DetectHiddenWindows
  DetectHiddenWindows true
  CustData:=NumGet(lParam,6*A_PtrSize,&quot;UInt&quot;)
  DlgX:=CustData&gt;&gt;16
  DlgY:=CustData&amp;0xFFFF
  WinMove DlgX,DlgY,,,&quot;ahk_id &quot; hwnd

  DetectHiddenWindows DetectSetting
  return 0
}

/*!
    Function- BGR2RGB(Color)
        Converts a BGR color value to a RGB one or vice versa.
    Parameters-
        Color - The BGR or RGB value to convert
    Returns-
        The converted value.
*/
BGR2RGB(Color)
{
  ret:=  (Color &amp; 0xFF000000)
      |((Color &amp; 0x00FF0000)&gt;&gt;16)
      | (Color &amp; 0x0000FF00)
      |((Color &amp; 0x000000FF)&lt;&lt;16)
  return ret
}

ObjLoad(addr,&amp;offset:=0) ; load dumped object to variable; from memory (addr is int) or from file (addr is string)
{
  ; #requires AutoHotkey v2.0.19
  If !IsNumber(addr)
  {
    If !FileExist(addr)
      return
    sz:=FileGetSize(addr)
    If !sz
      return
    Data:=FileRead(addr,&quot;RAW&quot;)
    addr:=Data.Ptr
    sz:=NumGet(addr,&quot;Int64&quot;)
    addr+=8
    obj:=0
  }
  ;    Sizes          1       1       2        2     4      4       8        8        8    x     x     x      x         x
  ;    Type.Enum: Char.1 UChar.2 Short.3 UShort.4 Int.5 UInt.6 Int64.7 UInt64.8 Double.9 Str.10 [].11 {}.12 map().13 buffer.14
  static types:=[&quot;Char&quot;,&quot;UChar&quot;,&quot;Short&quot;,&quot;UShort&quot;,&quot;Int&quot;,&quot;UInt&quot;,&quot;Int64&quot;,&quot;UInt64&quot;,&quot;Double&quot;]
  static syzes:=[     2,      2,      3,       3,    5,     5,      9,       9,       9]
  typ:=NumGet(addr,offset,&quot;Char&quot;)
  if (typ&lt;10)
  {
    obj:=NumGet(addr,offset+1,types[typ])
    offset+=syzes[typ]
  }
  else if (typ=10)
  {
    size:=NumGet(addr,offset+1,&quot;Int64&quot;)
    obj:=StrGet(addr+offset+9,size)
    offset+=9+size
  }
  else if (typ=11)
  {
    obj:=[]
    size:=NumGet(addr,offset+1,&quot;Int64&quot;)
    obj.Length:=size
    offset+=9
    loop size
      obj[a_index]:=ObjLoad(addr,&amp;offset)
  }
  else if (typ=12)
  {
    obj:={}
    Props:=NumGet(addr,offset+1,&quot;Int64&quot;)
    offset+=9
    loop Props
    {
      k:=ObjLoad(addr,&amp;offset)
      obj.%k%:=ObjLoad(addr,&amp;offset)
    }
  }
  else if (typ=13)
  {
    obj:=Map()
    Props:=NumGet(addr,offset+1,&quot;Int64&quot;)
    Items:=NumGet(addr,offset+9,&quot;Int64&quot;)
    offset+=17
    loop Props
    {
      k:=ObjLoad(addr,&amp;offset)
      obj.%k%:=ObjLoad(addr,&amp;offset)
    }
    loop Items
    {
      i:=ObjLoad(addr,&amp;offset)
      obj[i]:=ObjLoad(addr,&amp;offset)
    }
  }
  else if (typ=14)
  {
    size:=NumGet(addr,offset+1,&quot;Int64&quot;)
    obj:=Buffer(size)
    offset+=9
    loop size
      NumPut(&quot;UChar&quot;,NumGet(addr,offset+a_index-1,&quot;UChar&quot;),obj,a_index-1)
    offset+=size
  }
  return obj
}

ObjDump(Path,obj) ; dump object to file
{
  critical
  ; #requires AutoHotkey v2.0.19
  if !sz:=RawObjectSize(obj)
    return
  If FileExist(Path)
    FileDelete Path
  sz+=8
  Data:=Buffer(sz,0)
  ptr:=Data.Ptr
  NumPut(&quot;Int64&quot;,sz-8,ptr) ;size of all data
  RawObject(obj,ptr+8) ; use this for dumping to memory instead of ObjDump
  f:=FileOpen(Path,&quot;rw-rwd&quot;,&quot;CP0&quot;)
  Loop (sz//65536)
    f.RawWrite(ptr,65536),ptr+=65536
  f.RawWrite(ptr,Mod(sz,65536))
  f.Close()
  return sz
}
RawObject(obj,ptr,offset:=0) ; dump object to memory
{
  ; #requires AutoHotkey v2.0.19
  ;    Sizes          1       1       2        2     4      4       8        8        8    x     x     x      x         x
  ;    Type.Enum: Char.1 UChar.2 Short.3 UShort.4 Int.5 UInt.6 Int64.7 UInt64.8 Double.9 Str.10 [].11 {}.12 map().13 buffer.14
  if isObject(Obj)
  {
    if (Obj.base=[].base)
    {
      NumPut(&quot;Char&quot;,11,ptr,offset) ; type
      NumPut(&quot;Int64&quot;,Obj.Length,ptr,offset+1) ; Length of array
      offset+=9
      loop Obj.Length
        offset:=RawObject(Obj[a_index],ptr,offset)
    }
    else if (Obj.base={}.base)
    {
      NumPut(&quot;Char&quot;,12,ptr,offset) ; type
      NumPut(&quot;Int64&quot;,ObjOwnPropCount(Obj),ptr,offset+1) ; num of Props
      offset+=9
      for k,v in Obj.OwnProps()
      {
        offset:=RawObject(k,ptr,offset) ; name of prop (str)
        offset:=RawObject(v,ptr,offset) ; value of prop (any)
      }
    }
    else if (Obj.base=Map().base)
    {
      NumPut(&quot;Char&quot;,13,ptr,offset) ; type
      NumPut(&quot;Int64&quot;,ObjOwnPropCount(Obj),ptr,offset+1) ; num of Props
      NumPut(&quot;Int64&quot;,Obj.Count,ptr,offset+9) ; num of items
      offset+=17
      for k,v in Obj.OwnProps()
      {
        offset:=RawObject(k,ptr,offset) ; name of prop (str)
        offset:=RawObject(v,ptr,offset) ; value of prop (any)
      }
      for k,v in Obj
      {
        offset:=RawObject(k,ptr,offset) ; index of item (any)
        offset:=RawObject(v,ptr,offset) ; value of item (any)
      }
    }
    else if (Obj.base=Buffer().base)
    {
      NumPut(&quot;Char&quot;,14,ptr,offset) ; type
      NumPut(&quot;Int64&quot;,Obj.size,ptr,offset+1) ; num of bytes
      offset+=9
      loop Obj.size
        NumPut(&quot;UChar&quot;,NumGet(Obj,a_index-1,&quot;UChar&quot;),ptr,offset+a_index-1) ; Buffer content (RAW)
      offset+=Obj.size
    }
  }
  else if IsNumber(obj) ; num
  {
    NumPut(&quot;Char&quot;,IsFloat(Obj)?9:Obj&gt;4294967295?8       :Obj&gt;65535?6     :Obj&gt;255?4       :Obj&gt;-1?2      :Obj&gt;-129?1     :Obj&gt;-32769?3      :Obj&gt;-2147483649?5    :7,ptr,offset+0)
    NumPut(IsFloat(Obj)?&quot;Double&quot;:Obj&gt;4294967295?&quot;UInt64&quot;:Obj&gt;65535?&quot;UInt&quot;:Obj&gt;255?&quot;UShort&quot;:Obj&gt;-1?&quot;UChar&quot;:Obj&gt;-129?&quot;Char&quot;:Obj&gt;-32769?&quot;Short&quot;:Obj&gt;-2147483649?&quot;Int&quot;:&quot;Int64&quot;,Obj,ptr,offset+1)
    offset+=       IsFloat(Obj)||Obj&gt;4294967295?9       :Obj&gt;65535?5     :Obj&gt;255?3                      :Obj&gt;-129?2     :Obj&gt;-32769?3      :Obj&gt;-2147483649?5    :9
  }
  else ; string
  {
    NumPut(&quot;Char&quot;,10,ptr,offset)  ; type
    NumPut(&quot;Int64&quot;,sz:=StrPut(Obj,ptr+offset+9),ptr,offset+1) ; (str size) string text
    offset+=sz+9
  }
  return offset
}

RawObjectSize(Obj,sz:=0) ; get object&#039;s size in dumped condition
{
  ; #requires AutoHotkey v2.0.19
  if isObject(Obj)
  {
    if (Obj.base=[].base)
    {
      sz+=9 ; 1 (type) + 8 (num of items)
      loop Obj.Length
        sz:=RawObjectSize(Obj[a_index],sz)
    }
    else if (Obj.base={}.base)
    {
      sz+=9 ; 1 (type) + 8 (num of prop)
      for k,v in Obj.OwnProps()
      {
        sz:=RawObjectSize(k,sz) ; name of prop (str)
        sz:=RawObjectSize(v,sz) ; value of prop (any)
      }
    }
    else if (Obj.base=Map().base)
    {
      sz+=17 ; 1 (type) + 8 (size of Props) + 8 (size of array)
      for k,v in Obj.OwnProps()
      {
        sz:=RawObjectSize(k,sz) ; name of prop (str)
        sz:=RawObjectSize(v,sz) ; value of prop (any)
      }
      for k,v in Obj
      {
        sz:=RawObjectSize(k,sz) ; index
        sz:=RawObjectSize(v,sz) ; value
      }
    }
    else if (Obj.base=Buffer().base) ; sz = 1 (type) + 8 (size) + (buffer size)
      sz+=9+Obj.size
    else
    {
      MsgBox &quot;Unsupported object type: `&quot;&quot; Type(obj) &quot;`&quot;.&quot;,&quot;ObjDump&quot;,0x30
      return 0
    }
  }
  else if IsNumber(obj) ; sz = 1 (type) + (size of data type)
    sz+=IsFloat(Obj)||Obj&gt;4294967295?9:Obj&gt;65535?5:Obj&gt;255?3:Obj&gt;-129?2:Obj&gt;-32769?3:Obj&gt;-2147483649?5:9
  else ; sz = 1 (type) + 8 (size) + (str size)
    sz+=9+StrPut(Obj)
  return sz
}

</code></pre></div>]]></summary>
			<author>
				<name><![CDATA[Alectric]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=26027</uri>
			</author>
			<updated>2025-04-15T14:48:12Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?id=18426&amp;action=new</id>
		</entry>
		<entry>
			<title type="html"><![CDATA[AHK v2: Работа с Последовательным портом (COM Port)]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?id=18387&amp;action=new" />
			<summary type="html"><![CDATA[<p>Библиотека для работы с COM-портами в синхронном или асинхронном режимах.</p><p>Предназначена для обмена данными по Последовательным интерфейсам - COM Port. Функции, такие как: отправка данных в порт; получение ответных данных от порта; прослушка порта.<br />Позволяет работать в синхронном - однопоточном режиме, так и в асинхронном - многопоточном (в данном случае многопроцессовом) режиме.<br />Асинхронный режим предназначен для минимизации задержек при получении данных, для этого запускается второй процесс этого-же скрипта, который передает данные через &quot;общую&quot; память с помощью класса &quot;ClassMemory&quot; в основной процесс скрипта. Скрипт получает массив всех пройденных через порт пакетов и массив временных меток события прохождения пакета через порт. Для удобства обработки принятых данных добавлена возможность привязать функцию которая будет вызываться каждый раз при получении пакета данных.</p><div class="fancy_spoiler_switcher"><div class="fancy_spoiler_switcher_header"><strong>+</strong>&nbsp;Пример для синхронного режима</div><div class="fancy_spoiler"><div class="codebox"><pre><code>com10:=ASyncCOMPort()			; Создаем экземпляр объекта
com10.Connect(10)			; Подключаемся к COM10 с параметрами по умолчанию

if !com10.hPort
  msgbox &quot;Error&quot;

snd:=buffer(1000,0)			; Готовим буфер для отправки данных
rcv:=buffer(1000,0)			; Готовим буфер для приема данных

numput(&quot;Uint64&quot;,0x0102030405060708,snd) ; Готовим данные для отправки
com10.Write(snd,10)			; Отправляем
sleep 100				; Немного ждем
sz:=com10.Read(rcv,rcv.size)		; Получаем все принятые данные (если ответ был)

responce:=&quot;&quot;				; Отображаем принятые данные
loop sz
  responce.=numget(rcv,a_index-1,&quot;UChar&quot;) &quot; &quot;
msgbox responce
</code></pre></div></div></div><div class="fancy_spoiler_switcher"><div class="fancy_spoiler_switcher_header"><strong>+</strong>&nbsp;Пример для асинхронного режима</div><div class="fancy_spoiler"><div class="codebox"><pre><code>;----------------------------------------------;
#SingleInstance off				;
if (A_Args.Length=2)				;
{						;
  ASyncCOMPort()				 ; Эти строки всегда должны быть первыми в скрипте
  return					;
}						;
ComPort1:=ASyncCOMPort(100,,,,,,,OnRecieve)	; &quot;ComPort1&quot; можно использовать любое имя переменной
ComPort2:=ASyncCOMPort(100,,,,,,,OnRecieve)	; Любое количество экземпляров
;----------------------------------------------;
ComPort2.AceptSettings(10,38400,&quot;E&quot;,8,2)
HotKey &quot;Esc&quot;,fEsc ; !Не используй простые горячие клавиши (Esc::), используй команду &quot;HotKey&quot; в место этого! (HotKey &quot;Esc&quot;,MyFunc)
OnExit Exit

dots:=&quot;&quot;
COMPort:=&quot;&quot;
while !COMPort
{
  sleep 1000
  dots.=&quot;&gt;&quot;
  if (strlen(dots)&gt;20)
    dots:=&quot;&quot;
  COMPort:=SearchCOM(&quot;USB&quot;)
  if !COMPort
    tooltip &quot;COM Port не найден`nПоиск &quot; dots
}
tooltip
ComPort1.AceptSettings(COMPort)

while !ComPort1.COMOk ; Ждем успешного подключения к порту
{
  tooltip &quot;Пытаюсь подключиться к порту &quot; ComPort1.PortNum &quot; на скорости &quot; ComPort1.BaudRate
  sleep 1000
}

HotKey &quot;F1&quot;,f
tooltip &quot;Нажми F1 чтобы отправить данные в порт.`nEsc для выхода.&quot;

f(*)
{
  snd:=buffer(8,0) ; Готовим буфер для отправки
  numput(&quot;Uint64&quot;,0x0A84010000000301,snd) ; Готовим данные для отправки (запрос Modbus на 1 holding регистр начиная с 0 регистра)
  ComPort1.SendData(snd) ; Отправляем запрос
  ComPort2.SendData([1,2,3,4,5,6,7,8,9,10]) ; Отправляем данные в другой порт, если нужно
}

OnRecieve(Port)
{
  Num:=Port.Package.Length
  sz:=Port.Package[Num].Length		; читаем размер последнего полученного пакета
  responce:=&quot;&quot;				; выводим данные на экран
  loop sz
    responce.=Port.Package[Num][a_index] &quot; &quot;
  t:=FormatTime(Port.TimeStamp[Num][1],&quot;dd.MM.yyyy hh:mm:ss&quot;)
  msgbox Port.PortNum &quot;`nPackcage No&quot; Num &quot;`n&quot; responce &quot;`n&quot; t &quot;.&quot; Port.TimeStamp[Num][2]
}

fEsc(*)
{
  exitapp
}
exit(*)
{
  global
  ComPort1:=ComPort1.Delete()	; Корректное удаление объекта и очистка системной памяти
  ComPort2:=ComPort2.Delete()
  exitapp
}
</code></pre></div></div></div><div class="codebox"><pre><code>
/*
ASyncCOMPort by Alectric
https://forum.script-coding.com/
Static Method Call(Period:=0,PortNum:=0,BaudRate:=9600,Parity:=&quot;N&quot;,ByteSize:=8,StopBits:=1,ReadIT:=-1,OnRecieve:=0)
	Creates a new Object.
	If &quot;Period&quot; is presented uses multithread async mode, if not - simple sync mode. See examples below.
	Param &quot;ReadIT&quot; - value of ReadIntervalTimeOut and ReadTotalTimeoutConstant of COM port.
	If &quot;ReadIT&quot; param wasn&#039;t set it calculates automaticly.
	Param &quot;OnRecieve&quot; - Func object which will call when any packcage recieve.
	This function must accept at least 1 param - that will be &quot;this&quot; of an object or just use &quot;*&quot; as a param.

For multithread async mode only: -----------------------------------------------------------
Property Package:=[[1,2,3,4]]
	An array of arrays of COM port&#039;s buffer data, recieved and sended in order.

Property TimeStamp:=[[A_Now,ms,isSended]]
	An array of time stamps of each package and which package was sendded.

Method Clear()
	Clears both of arrays.

Property Period
	Change period of &quot;Main&quot; routine.

Property COMBusy
	Returns true if can&#039;t conect chousen COM Port.

Property GetCOMFail
	Returns last COMFail (see below).

Property GetCOMError
	Returns last COMError (see below).

Property COMOk
	Returns true if connection is Ok.

Property NDR
	Returns true if there is new data to read in &quot;Package&quot; array.

Property NewDataRecieved
	Returns true if there is new data to read and clears &quot;NDR&quot;.

Property SlavePID
	PID of child process.

Method AceptSettings(PortNum:=-1,BaudRate:=-1,Parity:=&quot;&quot;,ByteSize:=-1,StopBits:=-1,ReadIT:=-1)
	Apply defined settings and reconnect the port.

Method SendData(Var,size:=0)
	Send data to port. Var could be an array or buffer or string.

Property Diskonnect
	Set to true to disconnect COM port and stop connect attemtions.

Method Delete()
	To correctly delete an object and free system memory use construction like this: Port:=Port.Delete()

For sync mode only: -------------------------------------------------------------------------------
Method Connect(PortNum:=-1,BaudRate:=-1,Parity:=&quot;&quot;,ByteSize:=-1,StopBits:=-1,ReadIT:=-1)
	Connect to COM port with params. Or reconnect with new params.
	Param &quot;ReadIT&quot; - see above.
	If params is blank or omitted - uses previously setted value.
	Param &quot;PortNum&quot; must always be presented at first connection attempt.
	Returns port&#039;s file handle if success or 0 at fail.
	If failed - use &quot;COMFail&quot; and &quot;ErrorText&quot; props to determine what the problem is.

Method Write(lpBuffer,nNumberOfBytesToWrite)
	Write data to COM port.
	Returns number of written bytes.

Method Read(lpBuffer,nNumberOfBytesToRead)
	Reads data from COM port.
	Returns number of readed bytes.

Method ClearRXBuffer()
	Flush COMport&#039;s RX buffer.

Method Close(h:=0)
	Close Handle.
	If call without param - closing object&#039;s COM file handle and disconnects COM port.

Property hPort - Returns COM file handle.

Property COMFail
	Inside class number of error. If 0 - all ok.

Property ErrorText
	Text of COMFail error.

Property COMError
	A_LastError of failed DLL.

For any mode: ----------------------------------------------------------------------
Method CalcTimeout(BaudRate,ByteSize:=8)
	Returns approximate ReadIntervalTimeout for chosen BaudRate.

Function CheckCOMPortExist(Port)
	Port could be a string (&quot;COM10&quot;) or an integer.
	Returns true if COM port available.

Function SearchCOM(Name:=&quot;&quot;)
	Search available COM port by name or part of the name and retuns string like &quot;COM10&quot;.
	Returns empty string if port not available.
	If call without param - returns list of available COM ports in array of {Port:COMPort,Name:FriendlyName}.

Examples:
;-----------------------------------------------------------------------------------
; Usage in simple sync mode:
  com10:=ASyncCOMPort()			; Create instance of communication
  com10.Connect(10)			; Connect to COM10 with default params

  if !com10.hPort
    msgbox &quot;Error&quot;

  snd:=buffer(1000,0)			; Prepare send buffer
  rcv:=buffer(1000,0)			; Prepare recieve buffer

  numput(&quot;Uint64&quot;,0x0102030405060708,snd) ; Prepare data to send
  com10.Write(snd,10)			; Do send
  sleep 100				; wait to respond
  sz:=com10.Read(rcv,rcv.size)		; Recieve all available data

  responce:=&quot;&quot;				; show recieved data
  loop sz
    responce.=numget(rcv,a_index-1,&quot;UChar&quot;) &quot; &quot;
  msgbox responce

;-----------------------------------------------------------------------------------
; Usage in multithread async mode:
;----------------------------------------------;
#SingleInstance off				;
if (A_Args.Length=2)				;
{						;
  ASyncCOMPort()				 ; Always must be the first strings of the script
  return					;
}						;
ComPort1:=ASyncCOMPort(100,,,,,,,OnRecieve)	; &quot;ComPort1&quot; could be any var name
ComPort2:=ASyncCOMPort(100,,,,,,,OnRecieve)	; Any num of instances
;----------------------------------------------;
ComPort2.AceptSettings(10,38400,&quot;E&quot;,8,2)
HotKey &quot;Esc&quot;,fEsc ; !Do not use simple hotkeys (Esc::), use HotKey command instead! (HotKey &quot;Esc&quot;,MyFunc)
OnExit Exit

dots:=&quot;&quot;
COMPort:=&quot;&quot;
while !COMPort
{
  sleep 1000
  dots.=&quot;&gt;&quot;
  if (strlen(dots)&gt;20)
    dots:=&quot;&quot;
  COMPort:=SearchCOM(&quot;USB&quot;)
  if !COMPort
    tooltip &quot;COM port not found`nSearching &quot; dots
}
tooltip
ComPort1.AceptSettings(COMPort)

while !ComPort1.COMOk ; wait for connection
{
  tooltip &quot;Trying to connect to &quot; ComPort1.PortNum &quot; with baud &quot; ComPort1.BaudRate
  sleep 1000
}

HotKey &quot;F1&quot;,f
tooltip &quot;Press F1 to send data.`nEsc to exit.&quot;

f(*)
{
  snd:=buffer(8,0) ; Prepare buffer to send
  numput(&quot;Uint64&quot;,0x0A84010000000301,snd) ; prepare data to send (Modbus req for 1 holding register starting from 0)
  ComPort1.SendData(snd) ; Do send
  ComPort2.SendData([1,2,3,4,5,6,7,8,9,10]) ; Do send to another port
}

OnRecieve(Port)
{
  Num:=Port.Package.Length
  sz:=Port.Package[Num].Length		; read last packcage&#039;s size
  responce:=&quot;&quot;				; show recieved data
  loop sz
    responce.=Port.Package[Num][a_index] &quot; &quot;
  t:=FormatTime(Port.TimeStamp[Num][1],&quot;dd.MM.yyyy hh:mm:ss&quot;)
  msgbox Port.PortNum &quot;`nPackcage No&quot; Num &quot;`n&quot; responce &quot;`n&quot; t &quot;.&quot; Port.TimeStamp[Num][2]
}

fEsc(*)
{
  exitapp
}
exit(*)
{
  global
  ComPort1:=ComPort1.Delete()	; correctly deleting the objects and clear system memory
  ComPort2:=ComPort2.Delete()
  exitapp
}
;----------------------------------------------------------------------
*/
class ASyncCOMPort
{
  __Delete()
  {
    if this.hPort
      this.Close()
    this.Memory:=&quot;&quot;
    if !this.IsMain
      ExitApp
  }
  Delete()
  {
    settimer this.obm,0
    this.obm:=&quot;&quot;
  }
  Period
  {
    Set
    {
      settimer this.obm,this._Period:=Value
    }
    get =&gt; this._Period
  }
  Baud_List:=[50,75,110,150,300,600,1200,1800
	,2000,2400,3600,4800,7200,9600,14400
	,19200,28800,38400,57600,115200,250000
	,300000,375000,500000,750000,1500000,3000000]
  Parity_List:=[&quot;N&quot;,&quot;E&quot;,&quot;O&quot;,&quot;M&quot;,&quot;S&quot;]
  ByteSize_List:=[5,6,7,8]

  ; Memory offsets
  m:={   Diskonnect	:0	; UChar 1
	,ApplySettings	:1	; UChar 1
	,COMBusy	:2	; UChar 1
	,DataAvailable	:3	; UChar 1
	,SendDataNow	:4	; UChar 1
	,COMFail	:5	; UChar 1
	,COMError	:6	; Int   4
	,PortNum	:10	; Int   4
	,BaudRate	:14	; Int   4
	,Parity		:18	; Str   4	1 char
	,ByteSize	:22	; Int   4
	,StopBits	:26	; Int   4
	,ReadIT		:30	; Int   4
	,SendSize	:34	; Int   4
	,DataPos	:38	; Int   4
	,Packages	:42	; Int   4
	,SndOffset	:100	; Data  900
	,RcvOffset	:1000}	; Data  1047576

  __New(Period:=0,PortNum:=0,BaudRate:=9600,Parity:=&quot;N&quot;,ByteSize:=8,StopBits:=1,ReadIT:=-1,OnRecieve:=0)
  {
    if !IsInteger(PortNum)
      PortNum:=SearchCOM(PortNum)
    this.PortNum:=PortNum
    this.BaudRate:=BaudRate
    this.Parity:=Parity
    this.ByteSize:=ByteSize
    this.StopBits:=StopBits
    this.ReadIT:=ReadIT
    this.OnRecieve:=OnRecieve
    this.hPort:=0
    this.COMFail:=0
    this.ErrorText:=&quot;&quot;
    this.obm:=0
    this.DataSize:=1048576
    this.ReserveMemory:=10240

    this.IsMain:=false
    if (!A_Args.Length and Period)
    { ; Main
      this.IsMain:=true
      this.COMBusy:=false
      this.COMOk:=false
      this.ReadPos:=0
      this.NDR:=false
      this.Package:=[]
      this.TimeStamp:=[]
      this.Data:=Buffer(this.DataSize,0xff)
      loop this.m.RcvOffset
        NumPut(&quot;UChar&quot;,0,this.Data,a_index-1)
      this.MainPID:=DllCall(&quot;GetCurrentProcessId&quot;)
      if A_IsCompiled
        run A_ScriptFullPath &quot; &quot; this.MainPID &quot; &quot; this.Data.Ptr,,,&amp;SlavePID
      else
        run &quot;`&quot;&quot; A_AhkPath &quot;`&quot; `&quot;&quot; A_ScriptFullPath &quot;`&quot; &quot; this.MainPID &quot; &quot; this.Data.Ptr,,,&amp;SlavePID
      this.SlavePID:=SlavePID
      if PortNum
        this.AceptSettings(PortNum,BaudRate,Parity,ByteSize,StopBits,ReadIT)
      this.obm:=ObjBindMethod(this,&quot;Main&quot;)
      settimer this.obm,Period
    }
    else if (A_Args.Length=2)
    { ; Slave
      if (Period=0)
        Period:=1
      this.MainPID:=A_Args[1]
      this.pData:=A_Args[2]
      this.DataPos:=0
      this.Packages:=0
      obm:=ObjBindMethod(this,&quot;CheckPIDExist&quot;)
      settimer obm,2000
      this.MSM:=ClassMemory(&quot;Ahk_PID&quot; this.MainPID)
      this.Send_Data:=Buffer(this.m.RcvOffset-this.m.SndOffset)
      for k,v in this.m.OwnProps()
        this.m.%k%:=v+this.pData
      if PortNum
        this.Connect(PortNum,BaudRate,Parity,ByteSize,StopBits,ReadIT)
      this.obm:=ObjBindMethod(this,&quot;Slave&quot;)
      settimer this.obm,Period
    }
    this.OldBaudRate:=this.BaudRate
    this._Period:=Period
    return this
  }
  CheckPIDExist()
  {
    if !ProcessExist(this.MainPID)
      this.__Delete()
  }

  Clear()
  {
    this.Package:=[]
    this.TimeStamp:=[]
  }

  AceptSettings(PortNum:=-1,BaudRate:=-1,Parity:=&quot;&quot;,ByteSize:=-1,StopBits:=-1,ReadIT:=-1)
  {
    if NumGet(this.Data,this.m.ApplySettings,&quot;UChar&quot;)
      return
    if (PortNum!=-1)
      this.PortNum:=PortNum
    if IsInteger(this.PortNum)
      this.PortNum:=&quot;COM&quot; this.PortNum
    if (BaudRate!=-1)
      this.BaudRate:=BaudRate
    if (Parity!=&quot;&quot;)
      this.Parity:=Parity
    if (ByteSize!=-1)
      this.ByteSize:=ByteSize
    if (StopBits!=-1)
      this.StopBits:=StopBits
    if !Timeout:=this.CalcTimeout(this.BaudRate,this.ByteSize)
      Timeout:=1
    if (this.ReadIT=-1 or (ReadIT=-1 and this.OldBaudRate!=this.BaudRate))
      this.ReadIT:=Timeout
    this.OldBaudRate:=this.BaudRate
    PortNum:=IsInteger(this.PortNum)?this.PortNum:RegExReplace(this.PortNum,&quot;COM&quot;,&quot;&quot;)
    NumPut(&quot;Int&quot;,PortNum,this.Data,this.m.PortNum)
    NumPut(&quot;Int&quot;,this.BaudRate,this.Data,this.m.BaudRate)
    NumPut(&quot;Int&quot;,Ord(this.Parity),this.Data,this.m.Parity)
    NumPut(&quot;Int&quot;,this.ByteSize,this.Data,this.m.ByteSize)
    NumPut(&quot;Int&quot;,this.StopBits,this.Data,this.m.StopBits)
    NumPut(&quot;Int&quot;,this.ReadIT,this.Data,this.m.ReadIT)
    NumPut(&quot;UChar&quot;,0,this.Data,this.m.Diskonnect)
    NumPut(&quot;UChar&quot;,1,this.Data,this.m.ApplySettings)
  }

  SendData(Var,size:=0)
  {
    if (!this.COMOk or NumGet(this.Data,this.m.SendDataNow,&quot;UChar&quot;)) ; отправка еще не закончена
      return
    max:=this.m.RcvOffset-this.m.SndOffset
    if (Var.base=[].base)
    {
      size:=size?size:Var.Length
      if (size&gt;max)
      {
        Msgbox(&quot;Слишком много данных для отправки. &quot; size &quot; byte.`nМаксимально допустимо &quot; max &quot; byte.&quot;)
        return
      }
      loop size
        NumPut(&quot;UChar&quot;,Var[a_index],this.Data,this.m.SndOffset+a_index-1)
    }
    else if (Var.base=Buffer().base)
    {
      size:=size?size:Var.size
      if (size&gt;max)
      {
        Msgbox(&quot;Слишком много данных для отправки. &quot; size &quot; byte.`nМаксимально допустимо &quot; max &quot; byte.&quot;)
        return
      }
      loop size
        NumPut(&quot;UChar&quot;,NumGet(Var,a_index-1,&quot;UChar&quot;),this.Data,this.m.SndOffset+a_index-1)
    }
    else if (!IsObject(Var) and !IsNumber(Var)) ; string
    {
      size:=size?size:StrLen(Var)
      if (size&gt;max)
      {
        Msgbox(&quot;Слишком много данных для отправки. &quot; size &quot; byte.`nМаксимально допустимо &quot; max &quot; byte.&quot;)
        return
      }
      loop Parse Var
        NumPut(&quot;UChar&quot;,Ord(A_LoopField),this.Data,this.m.SndOffset+a_index-1)
    }
    NumPut(&quot;Int&quot;,size,this.Data,this.m.SendSize)
    NumPut(&quot;UChar&quot;,1,this.Data,this.m.SendDataNow)
    return true
  }

  Diskonnect
  {
    set
    {
      if this.IsMain
        NumPut(&quot;UChar&quot;,Value,this.Data,this.m.Diskonnect)
      else
        this.Close()
      return
    }
  }
  Main()
  {
    static _TimeStLen:=StrPut(A_Now,&quot;cp0&quot;)
    if NumGet(this.Data,this.m.Diskonnect,&quot;UChar&quot;)
    {
      this.COMOk:=false
      this.COMBusy:=false
      return
    }
    else if NumGet(this.Data,this.m.COMFail,&quot;UChar&quot;)
    {
      NumPut(&quot;UChar&quot;,0,this.Data,this.m.COMFail)
      this.COMOk:=false
      this.COMBusy:=false
      this.AceptSettings()
    }
    else if NumGet(this.Data,this.m.COMBusy,&quot;UChar&quot;)
    {
      NumPut(&quot;UChar&quot;,0,this.Data,this.m.COMBusy)
      this.COMOk:=false
      this.COMBusy:=true
    }
    else
    {
      this.COMOk:=true
      this.COMBusy:=false
;      ReadPack:=NumGet(this.Data,this.m.Packages,&quot;Int&quot;)
      DataValid:=NumGet(this.Data,this.m.RcvOffset+this.ReadPos,&quot;UChar&quot;)
      if (DataValid=0xFE)
      {
        this.ReadPos:=0
        return
      }
      else if (DataValid=1)
      {
        NumPut(&quot;UChar&quot;,0,this.Data,this.m.DataAvailable)
       pStatus:=this.m.RcvOffset+this.ReadPos+1
       pSize:=pStatus+1
        DataFromSlaveSize:=NumGet(this.Data,pSize,&quot;UShort&quot;)
       pData:=pSize+2
       pTimeSt:=pData+DataFromSlaveSize
       pTimeMs:=pTimeSt+_TimeStLen
        if (DataFromSlaveSize&lt;0 or DataFromSlaveSize&gt;4096) ;DefaultBReq
        {
          this.ReadPos:=NumGet(this.Data,this.m.DataPos,&quot;Int&quot;)
          return
        }
        NumPut(&quot;UChar&quot;,0XFF,this.Data,this.m.RcvOffset+this.ReadPos)
        NumPut(&quot;UShort&quot;,0xFFFF,this.Data,pSize)
        if (DataFromSlaveSize&gt;0)
        {
          DataArray:=[]
          loop DataFromSlaveSize
          {
            addr:=pData+a_index-1
            DataArray.Push(NumGet(this.Data,addr,&quot;UChar&quot;))
            NumPut(&quot;UChar&quot;,0xFF,this.Data,addr)
          }
          TimeSt:=StrGet(this.Data.Ptr+pTimeSt,_TimeStLen,&quot;cp0&quot;)
          loop _TimeStLen
            NumPut(&quot;UChar&quot;,0xFF,this.Data,pTimeSt+a_index-1)
          TimeMs:=NumGet(this.Data,pTimeMs,&quot;UShort&quot;)
          NumPut(&quot;UShort&quot;,0xFFFF,this.Data,pTimeMs)
          Status:=NumGet(this.Data,pStatus,&quot;UChar&quot;)
          NumPut(&quot;UChar&quot;,0xFF,this.Data,pStatus)
          this.ReadPos+=DataFromSlaveSize+_TimeStLen+6
          this.Package.Push(DataArray)
          this.TimeStamp.Push([TimeSt,TimeMs,Status=254?true:false])
          this.NDR:=true
          if (Status!=254 and this.OnRecieve.base=this.Main.base)
            this.OnRecieve()
        }
      }
    }
  }
  NewDataRecieved
  {
    get
    {
      if this.NDR
      {
        this.NDR:=false
        return true
      }
      return false
    }
  }
  GetCOMFail
  {
    get =&gt; NumGet(this.Data,this.m.COMFail,&quot;UChar&quot;)
  }
  GetCOMError
  {
    get =&gt; NumGet(this.Data,this.m.COMError,&quot;Int&quot;)
  }

  Slave()
  {
    static _TimeStLen:=StrPut(A_Now,&quot;cp0&quot;)
    static StartTime:=0,Frequency:=0,EndTime:=0
    static EmptyPacket:=2,RIT:=0
    static In_Data:=Buffer(8000)
    static SleepTimer:=a_tickcount
	/*
	Array =
	-----------
	DataValid	+0	byte	1
	Status		+1	byte	1
	Size		+2	short	2
	Data byte 0	+4	byte	1
	Data byte 1	+5	byte	1
	Data byte 2	+6	byte	1
	Data byte n	+7	byte	1
	Time Stamp	+n	str	_TimeStLen
	Time ms		+str+n	short	2
	-----------
	Next		+2+str+n

	if Size == 0xFFFFFFFE
	  End
	*/
;tooltip this.PortNum
    if this.MSM.Read(this.m.Diskonnect,&quot;UChar&quot;)
    {
;      this.Packages:=0
      this.Close()
    }
    else if this.MSM.Read(this.m.ApplySettings,&quot;UChar&quot;)
    {
      this.MSM.write(this.m.ApplySettings,0,&quot;UChar&quot;)
;      this.Packages:=0
      this.PortNum:=this.MSM.Read(this.m.PortNum,&quot;Int&quot;)
      this.BaudRate:=this.MSM.Read(this.m.BaudRate,&quot;Int&quot;)
      this.Parity:=Chr(this.MSM.Read(this.m.Parity,&quot;Int&quot;))
      this.ByteSize:=this.MSM.Read(this.m.ByteSize,&quot;Int&quot;)
      this.StopBits:=this.MSM.Read(this.m.StopBits,&quot;Int&quot;)
      this.ReadIT:=this.MSM.Read(this.m.ReadIT,&quot;Int&quot;)
      ;tooltip this.PortNum &quot;`n&quot; this.BaudRate &quot;`n&quot; this.Parity &quot;`n&quot; this.ByteSize &quot;`n&quot; this.StopBits &quot;`n&quot; this.ReadIT
      this.Connect(this.PortNum
		,this.BaudRate
		,this.Parity
		,this.ByteSize
		,this.StopBits
		,this.ReadIT)
      if !this.hPort
      {
        this.MSM.write(this.m.COMBusy,1,&quot;UChar&quot;)
        sleep 500
      }
    }
    if this.COMFail
    {
      this.MSM.write(this.m.COMFail,this.COMFail,&quot;UChar&quot;)
      this.MSM.write(this.m.COMError,this.COMError,&quot;Int&quot;)
      this.COMFail:=0
    }
    if this.hPort
    {
      if (this.MSM.Read(this.m.SendDataNow,&quot;UChar&quot;)=1)
      {
        size:=this.MSM.Read(this.m.SendSize,&quot;Int&quot;)
        if size
        {
          Send_Data:=Buffer(size)
          this.MSM.ReadRaw(this.m.SndOffset,Send_Data,size)
         pDataValid:=this.m.RcvOffset+this.DataPos
         pStatus:=pDataValid+1
         pSize:=pStatus+1
         pData:=pSize+2
         pTimeSt:=pData+size
         pTimeMs:=pTimeSt+_TimeStLen
          this.MSM.write(pStatus,254,&quot;uChar&quot;)
          this.MSM.write(pSize,size,&quot;uShort&quot;)
          this.MSM.WriteRaw(pData,Send_Data,size)
          str:=Buffer(_TimeStLen)
          TimeSt:=a_now
          TimeMs:=a_msec
          StrPut(TimeSt,str,_TimeStLen,&quot;cp0&quot;)
          this.MSM.writeRaw(pTimeSt,str,_TimeStLen)
          this.MSM.write(pTimeMs,TimeMs,&quot;UShort&quot;)
          this.DataPos+=6+_TimeStLen+size
          this.MSM.write(this.m.DataPos,this.DataPos,&quot;Int&quot;)
          this.Packages++
          this.MSM.write(this.m.Packages,this.Packages,&quot;Int&quot;)
          this.MSM.write(pDataValid,1,&quot;UChar&quot;)
          this.ClearRXBuffer()
          this.Write(Send_Data,size)
        }
        this.MSM.write(this.m.SendDataNow,2,&quot;UChar&quot;)
        DllCall(&quot;QueryPerformanceCounter&quot;,&quot;Int64*&quot;,&amp;StartTime)
      }
      RCV_Size:=this.Read(In_Data,1)
      if RCV_Size
      {
        Offset:=RCV_Size
        Packet:=false
        EmptyPack:=0
        Status:=0
        DllCall(&quot;QueryPerformanceCounter&quot;,&quot;Int64*&quot;,&amp;StartTime)
        loop (1000//this.ReadIT)
        {
          RCV_Size:=this.Read(In_Data.Ptr+Offset,4096)
          DllCall(&quot;QueryPerformanceFrequency&quot;,&quot;Int64*&quot;,&amp;Frequency)
          DllCall(&quot;QueryPerformanceCounter&quot;,&quot;Int64*&quot;,&amp;EndTime)
          RTimer:=(EndTime-StartTime)/Frequency*1000
          if RCV_Size
          {
            EmptyPack:=0
            Packet:=true
            DllCall(&quot;QueryPerformanceCounter&quot;,&quot;Int64*&quot;,&amp;StartTime)
            Offset+=RCV_Size
          }
          else if Packet
            EmptyPack++
          TimeOut:=RTimer&gt;(this.ReadIT*(EmptyPacket+4)) and RTimer&gt;7
          Empty:=(EmptyPack=EmptyPacket)
          if (TimeOut or Empty)
          {
            if TimeOut
              Status:=255
            if Empty
              Status:=EmptyPack
            if TimeOut
            {
              if (++this.ReadIT&gt;255)
                this.ReadIT:=1
              this.MSM.write(this.m.ReadIT,this.ReadIT,&quot;Int&quot;)
              RIT:=0
            }
            else
            {
              if (++RIT&gt;10)
              {
                RIT:=0
                if (--this.ReadIT&lt;1)
                  this.ReadIT:=1
                this.MSM.write(this.m.ReadIT,this.ReadIT,&quot;Int&quot;)
              }
            }
            this.MSM.write(this.m.SendDataNow,0,&quot;UChar&quot;)
            break
          }
        }
        RCV_Size:=Offset
        str:=Buffer(_TimeStLen)
        TimeSt:=a_now
        TimeMs:=a_msec
        StrPut(TimeSt,str,_TimeStLen,&quot;cp0&quot;)
       pDataValid:=this.m.RcvOffset+this.DataPos
       pStatus:=pDataValid+1
       pSize:=pStatus+1
       pData:=pSize+2
       pTimeSt:=pData+RCV_Size
       pTimeMs:=pTimeSt+_TimeStLen
        this.MSM.write(pStatus,Status,&quot;UChar&quot;)
        this.MSM.write(pSize,RCV_Size,&quot;UShort&quot;)
        this.MSM.WriteRaw(pData,In_Data,RCV_Size)
        this.MSM.writeRaw(pTimeSt,str,_TimeStLen)
        this.MSM.write(pTimeMs,TimeMs,&quot;UShort&quot;)
        this.DataPos+=6+_TimeStLen+RCV_Size
        this.MSM.write(this.m.DataPos,this.DataPos,&quot;Int&quot;)
        this.Packages++
        this.MSM.write(this.m.Packages,this.Packages,&quot;Int&quot;)
        this.MSM.write(pDataValid,1,&quot;UChar&quot;)
        RCV_Size:=0
        SleepTimer:=a_tickcount
      }
      else
      {
        DllCall(&quot;QueryPerformanceFrequency&quot;,&quot;Int64*&quot;,&amp;Frequency)
        DllCall(&quot;QueryPerformanceCounter&quot;,&quot;Int64*&quot;,&amp;EndTime)
        RTimer:=(EndTime-StartTime)/Frequency*1000
        if (RTimer&gt;100)
        {
          this.MSM.write(this.m.SendDataNow,0,&quot;UChar&quot;)
          DllCall(&quot;QueryPerformanceCounter&quot;,&quot;Int64*&quot;,&amp;StartTime)
        }
      }
      if (a_tickcount-SleepTimer&gt;30000)
        sleep 10

      if (this.DataPos&gt;this.DataSize-(this.m.RcvOffset-this.pData)-this.ReserveMemory)
      {
       pDataValid:=this.m.RcvOffset+this.DataPos
        this.MSM.write(pDataValid,0xFE,&quot;UChar&quot;)
        this.DataPos:=0
        this.MSM.write(this.m.DataPos,this.DataPos,&quot;Int&quot;)
        this.Packages:=0
        this.MSM.write(this.m.Packages,this.Packages,&quot;Int&quot;)
      }
    }
    else
      sleep 100
  }

  Write(lpBuffer,nNumberOfBytesToWrite)
  {
    lpNumberOfBytesWritten:=0
    Result:=DllCall(&quot;WriteFile&quot;
         ,&quot;Ptr&quot;,this.hPort
         ,&quot;Ptr&quot;,IsInteger(lpBuffer)?lpBuffer:lpBuffer.Ptr
         ,&quot;UInt&quot;,nNumberOfBytesToWrite
         ,&quot;UPtr*&quot;,&amp;lpNumberOfBytesWritten
         ,&quot;Ptr&quot;,0) ; lpOverlapped
    If !Result
    {
      this.COMFail:=8
      this.ErrorText:=
	(
	&quot;There is a problem with Serial Port communication.
	Failed Dll - WriteFile.
	LastError=&quot; A_LastError
        )
      this.COMError:=A_LastError
      this.Close()
      return 0
    }
    return lpNumberOfBytesWritten
  }

  Read(lpBuffer,nNumberOfBytesToRead)
  {
    lpNumberOfBytesReceived:=0
    Result:=DllCall(&quot;ReadFile&quot;
         ,&quot;Ptr&quot;,this.hPort
         ,&quot;Ptr&quot;,IsInteger(lpBuffer)?lpBuffer:lpBuffer.Ptr
         ,&quot;UInt&quot;,nNumberOfBytesToRead
         ,&quot;UPtr*&quot;,&amp;lpNumberOfBytesReceived
         ,&quot;Ptr&quot;,0) ; lpOverlapped
    If !Result
    {
      this.COMFail:=9
      this.ErrorText:=
	(
	&quot;There is a problem with Serial Port communication.
	Failed Dll - ReadFile.
	LastError=&quot; A_LastError
        )
      this.COMError:=A_LastError
      this.Close()
      return 0
    }
    return lpNumberOfBytesReceived
  }

  ClearRXBuffer()
  {
    Success:=DllCall(&quot;PurgeComm&quot;
         ,&quot;Ptr&quot;,this.hPort
         ,&quot;Int&quot;,0x8) ; dwFlag PURGE_RXCLEAR=0x08; PURGE_TXCLEAR=0x04
    return Success
  }

  ClearCommError()
  {
    return DllCall(&quot;ClearCommError&quot;,&quot;Ptr&quot;,this.hPort)
  }

  CalcTimeout(BaudRate,ByteSize:=8)
  {
    return Floor(((ByteSize+4)/BaudRate)*1000)
  }

  Connect(PortNum:=-1,BaudRate:=-1,Parity:=&quot;&quot;,ByteSize:=-1,StopBits:=-1,ReadIT:=-1)
  {
    if (PortNum!=-1)
      this.PortNum:=PortNum
    if IsInteger(this.PortNum)
      this.PortNum:=&quot;COM&quot; this.PortNum
    if (BaudRate!=-1)
      this.BaudRate:=BaudRate
    if (Parity!=&quot;&quot;)
      this.Parity:=Parity
    if (ByteSize!=-1)
      this.ByteSize:=ByteSize
    if (StopBits!=-1)
      this.StopBits:=StopBits

    if (ReadIT!=-1)
      this.ReadIT:=ReadIT
    if !Timeout:=this.CalcTimeout(this.BaudRate,this.ByteSize)
      Timeout:=1
    if (this.ReadIT=-1 or (ReadIT=-1 and this.OldBaudRate!=this.BaudRate))
      this.ReadIT:=Timeout
    this.OldBaudRate:=this.BaudRate

    if !CheckCOMPortExist(this.PortNum)
    {
      this.COMFail:=1
      this.ErrorText:=&quot;Port &quot; this.PortNum &quot; not exist.&quot;
      return 0
    }
    this.COMFail:=0
    this.ErrorText:=&quot;&quot;
    if this.IsMain
      return true
    if this.hPort
      this.Close()
    lpDef:=	 &quot;baud=&quot; this.BaudRate .
		&quot; parity=&quot; this.Parity .
		&quot; data=&quot; this.ByteSize .
		&quot; stop=&quot; this.StopBits .
		&quot; to=off xon=off odsr=off octs=off dtr=Off rts=off idsr=off&quot;
    lpDCB:=Buffer(28,0)
    if !DllCall(&quot;BuildCommDCB&quot;,&quot;Str&quot;,lpDef,&quot;Ptr&quot;,lpDCB.Ptr)
    {
      this.COMFail:=2
      this.ErrorText:=
	(
	&quot;There is a problem with Serial Port communication.
	Failed Dll - BuildCommDCB.
	LastError=&quot; A_LastError
        )
      this.COMError:=A_LastError
      return 0
    }
    hFile:=DllCall(&quot;CreateFile&quot;
       ,&quot;Str&quot;,&quot;\\.\&quot; this.PortNum	;lpFileName
       ,&quot;UInt&quot;,0xC0000000		;dwDesiredAccess GENERIC_READ=0x80000000 || GENERIC_WRITE=0x40000000
       ,&quot;UInt&quot;,3			;dwShareMode FILE_SHARE_READ=0x01 || FILE_SHARE_WRITE=0x02
       ,&quot;UInt&quot;,0			;lpSecurityAttributes
       ,&quot;UInt&quot;,3			;dwCreationDisposition CREATE_NEW=0x01 || CREATE_ALWAYS=0x02
       ,&quot;UInt&quot;,0			;dwFlagsAndAttributes
       ,&quot;Ptr&quot;,0				;hTemplateFile
       ,&quot;Ptr&quot;)
    if !hFile
    {
      this.COMFail:=3
      this.ErrorText:=
	(
	&quot;There is a problem with Serial Port communication.
	Failed Dll - CreateFile.
	LastError=&quot; A_LastError
        )
      this.COMError:=A_LastError
      return 0
    }
    if !DllCall(&quot;SetCommState&quot;,&quot;Ptr&quot;,hFile,&quot;Ptr&quot;,lpDCB.Ptr)
    {
      this.COMFail:=4
      this.ErrorText:=
	(
	&quot;There is a problem with Serial Port communication.
	Failed Dll - SetCommState.
	LastError=&quot; A_LastError
        )
      this.COMError:=A_LastError
      this.Close(hFile)
      return 0
    }
    lpCommTimeouts:=Buffer(20,0) ; 5 * sizeof(DWORD)
    NumPut(&quot;UInt&quot;,this.ReadIT	,lpCommTimeouts, 0) ;ReadIntervalTimeout -  ms, max Interval betwen characters
    NumPut(&quot;UInt&quot;,0		,lpCommTimeouts, 4) ;ReadTotalTimeoutMultiplier - ms * reqested number of bytes
    NumPut(&quot;UInt&quot;,this.ReadIT	,lpCommTimeouts, 8) ;ReadTotalTimeoutConstant - ms
    NumPut(&quot;UInt&quot;,100		,lpCommTimeouts,12) ;WriteTotalTimeoutMultiplier - ms * sended number of bytes
    NumPut(&quot;UInt&quot;,10000		,lpCommTimeouts,16) ;WriteTotalTimeoutConstant - ms
    if !DllCall(&quot;SetCommTimeouts&quot;,&quot;Ptr&quot;,hFile,&quot;Ptr&quot;,lpCommTimeouts.Ptr)
    {
      this.COMFail:=5
      this.ErrorText:=
	(
	&quot;There is a problem with Serial Port communication.
	Failed Dll - SetCommTimeouts.
	LastError=&quot; A_LastError
        )
      this.COMError:=A_LastError
      this.Close(hFile)
      return 0
    }
    return this.hPort:=hFile
  }

  Close(h:=0)
  {
    DllCall(&quot;CloseHandle&quot;,&quot;Ptr&quot;,(h?h:this.hPort))
    this.hPort:=0
  }

  WaitCommEvent(dwEvtMask:=0x3ff)
  {
  /*
  Events:
  EV_BREAK:=0x0040 ;A break was detected on input.
  EV_CTS:=0x0008 ;The CTS (clear-to-send) signal changed state.
  EV_DSR:=0x0010 ;The DSR (data-set-ready) signal changed state.
  EV_ERR:=0x0080 ;A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.
  EV_RING:=0x0100 ;A ring indicator was detected.
  EV_RLSD:=0x0020 ;The RLSD (receive-line-signal-detect) signal changed state.
  EV_RXCHAR:=0x0001 ;A character was received and placed in the input buffer.
  EV_RXFLAG:=0x0002 ;The event character was received and placed in the input buffer. The event character is specified in the device&#039;s DCB structure, which is applied to a serial port by using the SetCommState function.
  EV_TXEMPTY:=0x0004 ;The last character in the output buffer was sent.
  */
    MaskSet:=DllCall(&quot;SetCommMask&quot;
         ,&quot;Ptr&quot;,this.hPort
         ,&quot;int&quot;,dwEvtMask)
    if !MaskSet
    {
      this.COMFail:=6
      this.ErrorText:=
	(
	&quot;There is a problem with Serial Port communication.
	Failed Dll - SetCommMask.
	LastError=&quot; A_LastError
        )
      this.COMError:=A_LastError
      this.Close()
      return 0
    }
    lpEvtMask:=Buffer(4,0)
    Success:=DllCall(&quot;WaitCommEvent&quot;
         ,&quot;Ptr&quot;,this.hPort
         ,&quot;Ptr&quot;,lpEvtMask.Ptr
         ,&quot;Ptr&quot;,0) ; lpOverlapped
    if !Success
    {
      this.COMFail:=7
      this.ErrorText:=
	(
	&quot;There is a problem with Serial Port communication.
	Failed Dll - WaitCommEvent.
	LastError=&quot; A_LastError
        )
      this.COMError:=A_LastError
      this.Close()
      return 0
    }
    return 1
  }
}

CheckCOMPortExist(Port)
{
  if isinteger(Port)
    Port:=&quot;COM&quot; Port
  List:=SearchCOM()
  loop List.Length
  {
    o:=List[a_index]
    if (o.Port=Port)
      return true
  }
}
SearchCOM(Name:=&quot;&quot;)
{
  if !Name
    Out:=[]
  Loop Reg &quot;HKLM\HARDWARE\DEVICEMAP\SERIALCOMM\&quot;
  {
    COMPort:=RegRead()
    FriendlyName:=&quot;&quot;
    Loop Reg &quot;HKLM\SYSTEM\CurrentControlSet\Enum&quot;,&quot;KVR&quot;
    {
      if (A_LoopRegName=&quot;PortName&quot;)
      {
        Outputvar:=RegRead()
        if (Outputvar=COMPort)
        {
          FriendlyName:=RegRead(RegExReplace(A_LoopRegKey, &quot;(.*)\\Device Parameters&quot;, &quot;$1&quot;),&quot;FriendlyName&quot;,&quot;&quot;)
          if (Name and InStr(FriendlyName,Name))
            return COMPort
        }
      }
    }
    if (FriendlyName=&quot;&quot;)
      FriendlyName:=A_LoopRegName
    if !Name
      Out.Push({Port:COMPort,Name:FriendlyName})
    else if (InStr(A_LoopRegName,Name))
      return COMPort
  }
  if !Name
    return Out
  return &quot;&quot;
}


class ClassMemory
{
    ; List of useful accessible values. Some of these inherited values (the non objects) are set when the new operator is used.

    static aTypeSize := {UChar:	1,Char:		1
                    ,UShort:	2,Short:	2
                    ,UInt:	4,Int:		4
                    ,UFloat:	4,Float:	4
                    ,Int64:	8,Double:	8}
    static aRights := {PROCESS_ALL_ACCESS: 0x001F0FFF
			,PROCESS_CREATE_PROCESS: 0x0080
			,PROCESS_CREATE_THREAD: 0x0002
			,PROCESS_DUP_HANDLE: 0x0040
			,PROCESS_QUERY_INFORMATION: 0x0400
			,PROCESS_QUERY_LIMITED_INFORMATION: 0x1000
			,PROCESS_SET_INFORMATION: 0x0200
			,PROCESS_SET_QUOTA: 0x0100
			,PROCESS_SUSPEND_RESUME: 0x0800
			,PROCESS_TERMINATE: 0x0001
			,PROCESS_VM_OPERATION: 0x0008
			,PROCESS_VM_READ: 0x0010
			,PROCESS_VM_WRITE: 0x0020
			,SYNCHRONIZE: 0x00100000}

    ; Method:    __new(program, dwDesiredAccess := &quot;&quot;, byRef handle := &quot;&quot;, windowMatchMode := 3)
    ; Example:  derivedObject := new _ClassMemory(&quot;ahk_exe calc.exe&quot;)
    ;           This is the first method which should be called when trying to access a program&#039;s memory.
    ;           If the process is successfully opened, an object is returned which can be used to read that processes memory space.
    ;           [derivedObject].hProcess stores the opened handle.
    ;           If the target process closes and re-opens, simply free the derived object and use the new operator again to open a new handle.
    ; Parameters:
    ;   program             The program to be opened. This can be any AHK windowTitle identifier, such as
    ;                       ahk_exe, ahk_class, ahk_pid, or simply the window title. e.g. &quot;ahk_exe calc.exe&quot; or &quot;Calculator&quot;.
    ;                       It&#039;s safer not to use the window title, as some things can have the same window title e.g. an open folder called &quot;Starcraft II&quot;
    ;                       would have the same window title as the game itself.
    ;                       *&#039;DetectHiddenWindows, On&#039; is required for hidden windows*
    ;   dwDesiredAccess     The access rights requested when opening the process.
    ;                       If this parameter is null the process will be opened with the following rights
    ;                       PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_READ, PROCESS_VM_WRITE, &amp; SYNCHRONIZE
    ;                       This access level is sufficient to allow all of the methods in this class to work.
    ;                       Specific process access rights are listed here http://msdn.microsoft.com/en-us/library/windows/desktop/ms684880(v=vs.85).aspx
    ;   handle (Output)     Optional variable in which a copy of the opened processes handle will be stored.
    ;                       Values:
    ;                           Null    OpenProcess failed. The script may need to be run with admin rights admin,
    ;                                   and/or with the use of _ClassMemory.setSeDebugPrivilege(). Consult A_LastError for more information.
    ;                           0       The program isn&#039;t running (not found) or you passed an incorrect program identifier parameter.
    ;                                   In some cases _ClassMemory.setSeDebugPrivilege() may be required.
    ;                           Positive Integer    A handle to the process. (Success)
    ;   windowMatchMode -   Determines the matching mode used when finding the program (windowTitle).
    ;                       The default value is 3 i.e. an exact match. Refer to AHK&#039;s setTitleMathMode for more information.
    ; Return Values:
    ;   Object  On success an object is returned which can be used to read the processes memory.
    ;   Null    Failure. A_LastError and the optional handle parameter can be consulted for more information.


    __new(program, dwDesiredAccess := &quot;&quot;, handle := &quot;&quot;, windowMatchMode := 3)
    {
        DHW:=A_DetectHiddenWindows
        DetectHiddenWindows true
        if this.PID := handle := this.findPID(program, windowMatchMode) ; set handle to 0 if program not found
        {
            ; This default access level is sufficient to read and write memory addresses, and to perform pattern scans.
            ; if the program is run using admin privileges, then this script will also need admin privileges
            if !IsInteger(dwDesiredAccess)
                dwDesiredAccess := ClassMemory.aRights.PROCESS_QUERY_INFORMATION | ClassMemory.aRights.PROCESS_VM_OPERATION | ClassMemory.aRights.PROCESS_VM_READ | ClassMemory.aRights.PROCESS_VM_WRITE
            dwDesiredAccess |= ClassMemory.aRights.SYNCHRONIZE ; add SYNCHRONIZE to all handles to allow isHandleValid() to work

            if this.hProcess := handle := this.OpenProcess(this.PID, dwDesiredAccess) ; NULL/Blank if failed to open process for some reason
            {
                this.pNumberOfBytesRead := DllCall(&quot;GlobalAlloc&quot;, &quot;UInt&quot;, 0x0040, &quot;Ptr&quot;, A_PtrSize, &quot;Ptr&quot;) ; 0x0040 initialise to 0
                this.pNumberOfBytesWritten := DllCall(&quot;GlobalAlloc&quot;, &quot;UInt&quot;, 0x0040, &quot;Ptr&quot;, A_PtrSize, &quot;Ptr&quot;) ; initialise to 0

                this.readStringLastError := False
                this.currentProgram := program
                if this.isTarget64bit := this.isTargetProcess64Bit(this.PID, this.hProcess, dwDesiredAccess)
                    this.ptrType := &quot;Int64&quot;
                else this.ptrType := &quot;UInt&quot; ; If false or Null (fails) assume 32bit

                ; if script is 64 bit, getModuleBaseAddress() should always work
                ; if target app is truly 32 bit, then getModuleBaseAddress()
                ; will work when script is 32 bit
                if (A_PtrSize != 4 || !this.isTarget64bit)
                    this.BaseAddress := this.getModuleBaseAddress()

                ; If the above failed or wasn&#039;t called, fall back to alternate method
                if this.BaseAddress &lt; 0 || !this.BaseAddress
                    this.BaseAddress := this.getProcessBaseAddress(program, windowMatchMode)
                DetectHiddenWindows DHW
                return this
            }
        }
        DetectHiddenWindows DHW
        return
    }

    __delete()
    {
        this.closeHandle(this.hProcess)
        if this.pNumberOfBytesRead
            DllCall(&quot;GlobalFree&quot;, &quot;Ptr&quot;, this.pNumberOfBytesRead)
        if this.pNumberOfBytesWritten
            DllCall(&quot;GlobalFree&quot;, &quot;Ptr&quot;, this.pNumberOfBytesWritten)
        return
    }

    version()
    {
        return 9999.9999
    }

    findPID(program, windowMatchMode := &quot;3&quot;)
    {
        ; If user passes an AHK_PID, don&#039;t bother searching. There are cases where searching windows for PIDs
        ; wont work - console apps
        if RegExMatch(program, &quot;i)\s*AHK_PID\s+(0x[[:xdigit:]]+|\d+)&quot;, &amp;pid)
            return pid[1]
        if windowMatchMode
        {
            ; This is a string and will not contain the 0x prefix
            mode := A_TitleMatchMode
            ; remove hex prefix as SetTitleMatchMode will throw a run time error. This will occur if integer mode is set to hex and user passed an int (unquoted)
            windowMatchMode:=StrReplace(windowMatchMode,&quot;0x&quot;)
;            StringReplace, windowMatchMode, windowMatchMode, 0x
            SetTitleMatchMode windowMatchMode
        }
        pid:=WinGetPid(program)
        if windowMatchMode
            SetTitleMatchMode mode    ; In case executed in autoexec

        ; If use &#039;ahk_exe test.exe&#039; and winget fails (which can happen when setSeDebugPrivilege is required),
        ; try using the process command. When it fails due to setSeDebugPrivilege, setSeDebugPrivilege will still be required to openProcess
        ; This should also work for apps without windows.
        if (!pid &amp;&amp; RegExMatch(program, &quot;i)\bAHK_EXE\b\s*(.*)&quot;, &amp;fileNm))
        {
            ; remove any trailing AHK_XXX arguments
            filename := RegExReplace(fileNm[1], &quot;i)\bahk_(class|id|pid|group)\b.*&quot;, &quot;&quot;)
            filename := trim(filename)    ; extra spaces will make process command fail
            ; AHK_EXE can be the full path, so just get filename
            SplitPath fileName , &amp;fileName
            if (fileName) ; if filename blank, scripts own pid is returned
                pid := ProcessExist(fileName)
        }

        return pid ? pid : 0 ; PID is null on fail, return 0
    }

    ; Method:   isHandleValid()
    ;           This method provides a means to check if the internal process handle is still valid
    ;           or in other words, the specific target application instance (which you have been reading from)
    ;           has closed or restarted.
    ;           For example, if the target application closes or restarts the handle will become invalid
    ;           and subsequent calls to this method will return false.
    ;
    ; Return Values:
    ;   True    The handle is valid.
    ;   False   The handle is not valid.
    ;
    ; Notes:
    ;   This operation requires a handle with SYNCHRONIZE access rights.
    ;   All handles, even user specified ones are opened with the SYNCHRONIZE access right.

    isHandleValid()
    {
        return 0x102 = DllCall(&quot;WaitForSingleObject&quot;, &quot;Ptr&quot;, this.hProcess, &quot;UInt&quot;, 0)
        ; WaitForSingleObject return values
        ; -1 if called with null hProcess (sets lastError to 6 - invalid handle)
        ; 258 / 0x102 WAIT_TIMEOUT - if handle is valid (process still running)
        ; 0  WAIT_OBJECT_0 - if process has terminated
    }

    ; Method:   openProcess(PID, dwDesiredAccess)
    ;           ***Note:    This is an internal method which shouldn&#039;t be called directly unless you absolutely know what you are doing.
    ;                       This is because the new operator, in addition to calling this method also sets other values
    ;                       which are required for the other methods to work correctly.
    ; Parameters:
    ;   PID                 The Process ID of the target process.
    ;   dwDesiredAccess     The access rights requested when opening the process.
    ;                       Specific process access rights are listed here http://msdn.microsoft.com/en-us/library/windows/desktop/ms684880(v=vs.85).aspx
    ; Return Values:
    ;   Null/blank          OpenProcess failed. If the target process has admin rights, then the script also needs to be ran as admin.
    ;                       _ClassMemory.setSeDebugPrivilege() may also be required.
    ;   Positive integer    A handle to the process.

    openProcess(PID, dwDesiredAccess)
    {
        r := DllCall(&quot;OpenProcess&quot;, &quot;UInt&quot;, dwDesiredAccess, &quot;Int&quot;, False, &quot;UInt&quot;, PID, &quot;Ptr&quot;)
        ; if it fails with 0x5 ERROR_ACCESS_DENIED, try enabling privilege ... lots of users never try this.
        ; there may be other errors which also require DebugPrivilege....
        if (!r &amp;&amp; A_LastError = 5)
        {
            this.setSeDebugPrivilege(true) ; no harm in enabling it if it is already enabled by user
            if (r2 := DllCall(&quot;OpenProcess&quot;, &quot;UInt&quot;, dwDesiredAccess, &quot;Int&quot;, False, &quot;UInt&quot;, PID, &quot;Ptr&quot;))
                return r2
            DllCall(&quot;SetLastError&quot;, &quot;UInt&quot;, 5) ; restore original error if it doesnt work
        }
        ; If fails with 0x5 ERROR_ACCESS_DENIED (when setSeDebugPrivilege() is req.), the func. returns 0 rather than null!! Set it to null.
        ; If fails for another reason, then it is null.
        return r ? r : 0
    }

    ; Method:   closeHandle(hProcess)
    ;           Note:   This is an internal method which is automatically called when the script exits or the derived object is freed/destroyed.
    ;                   There is no need to call this method directly. If you wish to close the handle simply free the derived object.
    ;                   i.e. derivedObject := [] ; or derivedObject := &quot;&quot;
    ; Parameters:
    ;   hProcess        The handle to the process, as returned by openProcess().
    ; Return Values:
    ;   Non-Zero        Success
    ;   0               Failure

    closeHandle(hProcess)
    {
        return DllCall(&quot;CloseHandle&quot;, &quot;Ptr&quot;, hProcess)
    }

    ; Methods:      numberOfBytesRead() / numberOfBytesWritten()
    ;               Returns the number of bytes read or written by the last ReadProcessMemory or WriteProcessMemory operation.
    ;
    ; Return Values:
    ;   zero or positive value      Number of bytes read/written
    ;   -1                          Failure. Shouldn&#039;t occur

    numberOfBytesRead()
    {
        return !this.pNumberOfBytesRead ? -1 : NumGet(this.pNumberOfBytesRead+0, &quot;Ptr&quot;)
    }
    numberOfBytesWritten()
    {
        return !this.pNumberOfBytesWritten ? -1 : NumGet(this.pNumberOfBytesWritten+0, &quot;Ptr&quot;)
    }


    ; Method:   read(address, type := &quot;UInt&quot;, aOffsets*)
    ;           Reads various integer type values
    ; Parameters:
    ;       address -   The memory address of the value or if using the offset parameter,
    ;                   the base address of the pointer.
    ;       type    -   The integer type.
    ;                   Valid types are UChar, Char, UShort, Short, UInt, Int, Float, Int64 and Double.
    ;                   Note: Types must not contain spaces i.e. &quot; UInt&quot; or &quot;UInt &quot; will not work.
    ;                   When an invalid type is passed the method returns NULL and sets ErrorLevel to -2
    ;       aOffsets* - A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
    ;                   The address (base address) and offsets should point to the memory address which holds the integer.
    ; Return Values:
    ;       integer -   Indicates success.
    ;       Null    -   Indicates failure. Check ErrorLevel and A_LastError for more information.
    ;       Note:       Since the returned integer value may be 0, to check for success/failure compare the result
    ;                   against null i.e. if (result = &quot;&quot;) then an error has occurred.
    ;                   When reading doubles, adjusting &quot;SetFormat, float, totalWidth.DecimalPlaces&quot;
    ;                   may be required depending on your requirements.

    read(address, type := &quot;UInt&quot;)
    {
        ; If invalid type RPM() returns success (as bytes to read resolves to null in dllCall())
        ; so set errorlevel to invalid parameter for DLLCall() i.e. -2
        if !ClassMemory.aTypeSize.%type%
            return 0
        result:=0
        if DllCall(&quot;ReadProcessMemory&quot;, &quot;Ptr&quot;, this.hProcess, &quot;Ptr&quot;, address, type &quot;*&quot;, &amp;result, &quot;Ptr&quot;, ClassMemory.aTypeSize.%type%, &quot;Ptr&quot;, this.pNumberOfBytesRead)
            return result
        return
    }

    ; Method:   readRaw(address, byRef buffer, bytes := 4, aOffsets*)
    ;           Reads an area of the processes memory and stores it in the buffer variable
    ; Parameters:
    ;       address  -  The memory address of the area to read or if using the offsets parameter
    ;                   the base address of the pointer which points to the memory region.
    ;       buffer   -  The unquoted variable name for the buffer. This variable will receive the contents from the address space.
    ;                   This method calls varsetCapcity() to ensure the variable has an adequate size to perform the operation.
    ;                   If the variable already has a larger capacity (from a previous call to varsetcapcity()), then it will not be shrunk.
    ;                   Therefore it is the callers responsibility to ensure that any subsequent actions performed on the buffer variable
    ;                   do not exceed the bytes which have been read - as these remaining bytes could contain anything.
    ;       bytes   -   The number of bytes to be read.
    ;       aOffsets* - A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
    ;                   The address (base address) and offsets should point to the memory address which is to be read
    ; Return Values:
    ;       Non Zero -   Indicates success.
    ;       Zero     -   Indicates failure. Check errorLevel and A_LastError for more information
    ;
    ; Notes:            The contents of the buffer may then be retrieved using AHK&#039;s NumGet() and StrGet() functions.
    ;                   This method offers significant (~30% and up) performance boost when reading large areas of memory.
    ;                   As calling ReadProcessMemory for four bytes takes a similar amount of time as it does for 1,000 bytes.

    readRaw(address, buffer, bytes := 4)
    {
;        Data.__new(bytes)
        return DllCall(&quot;ReadProcessMemory&quot;, &quot;Ptr&quot;, this.hProcess, &quot;Ptr&quot;, address, &quot;Ptr&quot;,IsInteger(buffer)?buffer:buffer.ptr, &quot;Ptr&quot;, bytes, &quot;Ptr&quot;, this.pNumberOfBytesRead)
    }

    ; Method:   write(address, value, type := &quot;Uint&quot;, aOffsets*)
    ;           Writes various integer type values to the process.
    ; Parameters:
    ;       address -   The memory address to which data will be written or if using the offset parameter,
    ;                   the base address of the pointer.
    ;       type    -   The integer type.
    ;                   Valid types are UChar, Char, UShort, Short, UInt, Int, Float, Int64 and Double.
    ;                   Note: Types must not contain spaces i.e. &quot; UInt&quot; or &quot;UInt &quot; will not work.
    ;                   When an invalid type is passed the method returns NULL and sets ErrorLevel to -2
    ;       aOffsets* - A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
    ;                   The address (base address) and offsets should point to the memory address which is to be written to.
    ; Return Values:
    ;       Non Zero -  Indicates success.
    ;       Zero     -  Indicates failure. Check errorLevel and A_LastError for more information
    ;       Null    -   An invalid type was passed. Errorlevel is set to -2

    write(address, value, type := &quot;Uint&quot;)
    {
        if !ClassMemory.aTypeSize.%type%
            return false
        return DllCall(&quot;WriteProcessMemory&quot;, &quot;Ptr&quot;, this.hProcess, &quot;Ptr&quot;, address, type &quot;*&quot;, &amp;value, &quot;Ptr&quot;, ClassMemory.aTypeSize.%type%, &quot;Ptr&quot;, this.pNumberOfBytesWritten)
    }

    ; Method:   writeRaw(address, pBuffer, sizeBytes, aOffsets*)
    ;           Writes a buffer to the process.
    ; Parameters:
    ;   address -       The memory address to which the contents of the buffer will be written
    ;                   or if using the offset parameter, the base address of the pointer.
    ;   pBuffer -       A pointer to the buffer which is to be written.
    ;                   This does not necessarily have to be the beginning of the buffer itself e.g. pBuffer := &amp;buffer + offset
    ;   sizeBytes -     The number of bytes which are to be written from the buffer.
    ;   aOffsets* -     A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
    ;                   The address (base address) and offsets should point to the memory address which is to be written to.
    ; Return Values:
    ;       Non Zero -  Indicates success.
    ;       Zero     -  Indicates failure. Check errorLevel and A_LastError for more information

    writeRaw(address, Buffer, sizeBytes)
    {
        return DllCall(&quot;WriteProcessMemory&quot;, &quot;Ptr&quot;, this.hProcess, &quot;Ptr&quot;, address, &quot;Ptr&quot;,IsInteger(Buffer)?Buffer:Buffer.Ptr, &quot;Ptr&quot;, sizeBytes, &quot;Ptr&quot;, this.pNumberOfBytesWritten)
    }

    ; Interesting note:
    ; Although handles are 64-bit pointers, only the less significant 32 bits are employed in them for the purpose
    ; of better compatibility (for example, to enable 32-bit and 64-bit processes interact with each other)
    ; Here are examples of such types: HANDLE, HWND, HMENU, HPALETTE, HBITMAP, etc.
    ; http://www.viva64.com/en/k/0005/



    ; Method:   getProcessBaseAddress(WindowTitle, windowMatchMode := 3)
    ;           Returns the base address of a process. In most cases this will provide the same result as calling getModuleBaseAddress() (when passing
    ;           a null value as the module parameter), however getProcessBaseAddress() will usually work regardless of the bitness
    ;           of both the AHK exe and the target process.
    ;           *This method relies on the target process having a window and will not work for console apps*
    ;           *&#039;DetectHiddenWindows, On&#039; is required for hidden windows*
    ;           ***If this returns an incorrect value, try using (the MORE RELIABLE) getModuleBaseAddress() instead.***
    ; Parameters:
    ;   windowTitle         This can be any AHK windowTitle identifier, such as
    ;                       ahk_exe, ahk_class, ahk_pid, or simply the window title. e.g. &quot;ahk_exe calc.exe&quot; or &quot;Calculator&quot;.
    ;                       It&#039;s safer not to use the window title, as some things can have the same window title e.g. an open folder called &quot;Starcraft II&quot;
    ;                       would have the same window title as the game itself.
    ;   windowMatchMode     Determines the matching mode used when finding the program&#039;s window (windowTitle).
    ;                       The default value is 3 i.e. an exact match. The current matchmode will be used if the parameter is null or 0.
    ;                       Refer to AHK&#039;s setTitleMathMode for more information.
    ; Return Values:
    ;   Positive integer    The base address of the process (success).
    ;   Null                The process&#039;s window couldn&#039;t be found.
    ;   0                   The GetWindowLong or GetWindowLongPtr call failed. Try getModuleBaseAddress() instead.


    getProcessBaseAddress(windowTitle, windowMatchMode := &quot;3&quot;)
    {
        mode:=0
        if (windowMatchMode &amp;&amp; A_TitleMatchMode != windowMatchMode)
        {
            mode := A_TitleMatchMode ; This is a string and will not contain the 0x prefix
            windowMatchMode:=StrReplace(windowMatchMode,&quot;0x&quot;)
;            StringReplace windowMatchMode, windowMatchMode, 0x ; remove hex prefix as SetTitleMatchMode will throw a run time error. This will occur if integer mode is set to hex and matchmode param is passed as an number not a string.
            SetTitleMatchMode windowMatchMode    ;mode 3 is an exact match
        }
        hWnd:=WinGetID(WindowTitle)
        if mode
            SetTitleMatchMode mode    ; In case executed in autoexec
        if !hWnd
            return 0 ; return blank failed to find window
      ; GetWindowLong returns a Long (Int) and GetWindowLongPtr return a Long_Ptr
        return DllCall(A_PtrSize = 4     ; If DLL call fails, returned value will = 0
            ? &quot;GetWindowLong&quot;
            : &quot;GetWindowLongPtr&quot;
            , &quot;Ptr&quot;, hWnd, &quot;Int&quot;, -6, A_Is64bitOS ? &quot;Int64&quot; : &quot;UInt&quot;)
            ; For the returned value when the OS is 64 bit use Int64 to prevent negative overflow when AHK is 32 bit and target process is 64bit
            ; however if the OS is 32 bit, must use UInt, otherwise the number will be huge (however it will still work as the lower 4 bytes are correct)
            ; Note - it&#039;s the OS bitness which matters here, not the scripts/AHKs
    }

    ; http://winprogger.com/getmodulefilenameex-enumprocessmodulesex-failures-in-wow64/
    ; http://stackoverflow.com/questions/3801517/how-to-enum-modules-in-a-64bit-process-from-a-32bit-wow-process

    ; Method:            getModuleBaseAddress(module := &quot;&quot;, byRef aModuleInfo := &quot;&quot;)
    ; Parameters:
    ;   moduleName -    The file name of the module/dll to find e.g. &quot;calc.exe&quot;, &quot;GDI32.dll&quot;, &quot;Bass.dll&quot; etc
    ;                   If no module (null) is specified, the address of the base module - main()/process will be returned
    ;                   e.g. for calc.exe the following two method calls are equivalent getModuleBaseAddress() and getModuleBaseAddress(&quot;calc.exe&quot;)
    ;   aModuleInfo -   (Optional) A module Info object is returned in this variable. If method fails this variable is made blank.
    ;                   This object contains the keys: name, fileName, lpBaseOfDll, SizeOfImage, and EntryPoint
    ; Return Values:
    ;   Positive integer - The module&#039;s base/load address (success).
    ;   -1 - Module not found
    ;   -3 - EnumProcessModulesEx failed
    ;   -4 - The AHK script is 32 bit and you are trying to access the modules of a 64 bit target process. Or the target process has been closed.
    ; Notes:    A 64 bit AHK can enumerate the modules of a target 64 or 32 bit process.
    ;           A 32 bit AHK can only enumerate the modules of a 32 bit process
    ;           This method requires PROCESS_QUERY_INFORMATION + PROCESS_VM_READ access rights. These are included by default with this class.

    getModuleBaseAddress()
    {
        moduleName := this.GetModuleFileNameEx(0, True) ; main executable module of the process - get just fileName no path
        aModules:=map()
        if (r := this.getModules(&amp;aModules, True) &lt; 0)
            return r ; -4, -3
        return aModules.has(moduleName) ? (aModules[moduleName].lpBaseOfDll) : -1
        ; no longer returns -5 for failed to get module info
    }

    ; SeDebugPrivileges is required to read/write memory in some programs.
    ; This only needs to be called once when the script starts,
    ; regardless of the number of programs being read (or if the target programs restart)
    ; Call this before attempting to call any other methods in this class
    ; i.e. call _ClassMemory.setSeDebugPrivilege() at the very start of the script.

    setSeDebugPrivilege(enable := True)
    {
        h := DllCall(&quot;OpenProcess&quot;, &quot;UInt&quot;, 0x0400, &quot;Int&quot;, false, &quot;UInt&quot;, DllCall(&quot;GetCurrentProcessId&quot;), &quot;Ptr&quot;)
        ; Open an adjustable access token with this process (TOKEN_ADJUST_PRIVILEGES = 32)
        t:=0
        DllCall(&quot;Advapi32.dll\OpenProcessToken&quot;, &quot;Ptr&quot;, h, &quot;UInt&quot;, 32, &quot;PtrP&quot;, &amp;t)
        ti:=Buffer(16,0)
;        VarSetCapacity(ti, 16, 0)  ; structure of privileges
        NumPut(&quot;UInt&quot;,1, ti, 0)  ; one entry in the privileges array...
        ; Retrieves the locally unique identifier of the debug privilege:
        luid:=0
        DllCall(&quot;Advapi32.dll\LookupPrivilegeValue&quot;, &quot;Ptr&quot;, 0, &quot;Str&quot;, &quot;SeDebugPrivilege&quot;, &quot;Int64P&quot;, &amp;luid)
        NumPut(&quot;Int64&quot;,luid, ti, 4)
        if enable
            NumPut(&quot;UInt&quot;,2, ti, 12)  ; enable this privilege: SE_PRIVILEGE_ENABLED = 2
        ; Update the privileges of this process with the new access token:
        r := DllCall(&quot;Advapi32.dll\AdjustTokenPrivileges&quot;, &quot;Ptr&quot;, t, &quot;Int&quot;, false, &quot;Ptr&quot;, ti.Ptr, &quot;UInt&quot;, 0, &quot;Ptr&quot;, 0, &quot;Ptr&quot;, 0)
        DllCall(&quot;CloseHandle&quot;, &quot;Ptr&quot;, t)  ; close this access token handle to save memory
        DllCall(&quot;CloseHandle&quot;, &quot;Ptr&quot;, h)  ; close this process handle to save memory
        return r
    }


    ; Method:  isTargetProcess64Bit(PID, hProcess := &quot;&quot;, currentHandleAccess := &quot;&quot;)
    ;          Determines if a process is 64 bit.
    ; Parameters:
    ;   PID                     The Process ID of the target process. If required this is used to open a temporary process handle.
    ;   hProcess                (Optional) A handle to the process, as returned by openProcess() i.e. [derivedObject].hProcess
    ;   currentHandleAccess     (Optional) The dwDesiredAccess value used when opening the process handle which has been
    ;                           passed as the hProcess parameter. If specifying hProcess, you should also specify this value.
    ; Return Values:
    ;   True    The target application is 64 bit.
    ;   False   The target application is 32 bit.
    ;   Null    The method failed.
    ; Notes:
    ;   This is an internal method which is called when the new operator is used. It is used to set the pointer type for 32/64 bit applications so the pointer methods will work.
    ;   This operation requires a handle with PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION access rights.
    ;   If the currentHandleAccess parameter does not contain these rights (or not passed) or if the hProcess (process handle) is invalid (or not passed)
    ;   a temporary handle is opened to perform this operation. Otherwise if hProcess and currentHandleAccess appear valid
    ;   the passed hProcess is used to perform the operation.

    isTargetProcess64Bit(PID, hProcess := 0, currentHandleAccess := 0)
    {
        closeHandle:=0
        if !A_Is64bitOS
            return False
        ; If insufficient rights, open a temporary handle
        else if !hProcess || !(currentHandleAccess &amp; (ClassMemory.aRights.PROCESS_QUERY_INFORMATION | ClassMemory.aRights.PROCESS_QUERY_LIMITED_INFORMATION))
            closeHandle := hProcess := this.openProcess(PID, ClassMemory.aRights.PROCESS_QUERY_INFORMATION)
        Wow64Process:=0
        if (hProcess &amp;&amp; DllCall(&quot;IsWow64Process&quot;, &quot;Ptr&quot;, hProcess, &quot;Int*&quot;, &amp;Wow64Process))
            result := !Wow64Process
        if closeHandle
          this.CloseHandle(hProcess)
        return result
    }
    /*
        _Out_  PBOOL Wow64Proces value set to:
        True if the process is running under WOW64 - 32bit app on 64bit OS.
        False if the process is running under 32-bit Windows!
        False if the process is a 64-bit application running under 64-bit Windows.
    */

    getModules(&amp;aModules, useFileNameAsKey := False)
    {
        if (A_PtrSize = 4 &amp;&amp; this.IsTarget64bit)
            return -4 ; AHK is 32bit and target process is 64 bit, this function wont work
;        aModules := []
        lphModule:=Buffer(4)
        if !moduleCount := this.EnumProcessModulesEx(&amp;lphModule)
            return -3
        loop moduleCount
        {
            aModuleInfo:=map()
            hModule := numget(lphModule, (A_index - 1) * A_PtrSize,&quot;Ptr&quot;)
            this.GetModuleInformation(hModule, &amp;aModuleInfo)
            aModuleInfo.Name := this.GetModuleFileNameEx(hModule)
            filePath := aModuleInfo.name
            SplitPath filePath, &amp;fileName
            aModuleInfo.fileName := fileName
            if useFileNameAsKey
                aModules[fileName] := aModuleInfo
            else aModules.insert(aModuleInfo)
        }
        return moduleCount
    }

    ; lpFilename [out]
    ; A pointer to a buffer that receives the fully qualified path to the module.
    ; If the size of the file name is larger than the value of the nSize parameter, the function succeeds
    ; but the file name is truncated and null-terminated.
    ; If the buffer is adequate the string is still null terminated.

    GetModuleFileNameEx(hModule := 0, fileNameNoPath := False)
    {
        ; ANSI MAX_PATH = 260 (includes null) - unicode can be ~32K.... but no one would ever have one that size
        ; So just give it a massive size and don&#039;t bother checking. Most coders just give it MAX_PATH size anyway
        VarSetStrCapacity(&amp;lpFilename,1024)
;        VarSetCapacity(lpFilename, 2048 * (A_IsUnicode ? 2 : 1))
        DllCall(&quot;psapi\GetModuleFileNameEx&quot;
                    , &quot;Ptr&quot;, this.hProcess
                    , &quot;Ptr&quot;, hModule
                    , &quot;Str&quot;, lpFilename
                    , &quot;Uint&quot;, 1024)
        if fileNameNoPath
            SplitPath lpFilename, &amp;lpFilename ; strips the path so = GDI32.dll

        return lpFilename
    }



    ; dwFilterFlag
    ;   LIST_MODULES_DEFAULT    0x0
    ;   LIST_MODULES_32BIT      0x01
    ;   LIST_MODULES_64BIT      0x02
    ;   LIST_MODULES_ALL        0x03
    ; If the function is called by a 32-bit application running under WOW64, the dwFilterFlag option
    ; is ignored and the function provides the same results as the EnumProcessModules function.
    EnumProcessModulesEx(&amp;lphModule, dwFilterFlag := 0x03)
    {
;        lastError := A_LastError
        size := 4 ; VarSetCapacity(lphModule, 4)
        reqSize:=0
        loop
        {
            if !DllCall(&quot;psapi\EnumProcessModulesEx&quot;
                        , &quot;Ptr&quot;, this.hProcess
                        , &quot;Ptr&quot;, lphModule.Ptr
                        , &quot;Uint&quot;, size
                        , &quot;Uint*&quot;, &amp;reqSize
                        , &quot;Uint&quot;, dwFilterFlag)
                return 0
            else if (size &gt;= reqSize)
                break
            else
            {
                size := reqSize
                lphModule.__new(size)
            }
        }
        ; On first loop it fails with A_lastError = 0x299 as its meant to
        ; might as well reset it to its previous version
;        DllCall(&quot;SetLastError&quot;, &quot;UInt&quot;, lastError)
        return reqSize // A_PtrSize ; module count  ; sizeof(HMODULE) - enumerate the array of HMODULEs
    }

    GetModuleInformation(hModule,&amp;aModuleInfo)
    {
        MODULEINFO:=buffer(A_PtrSize * 3)
;        VarSetCapacity(MODULEINFO, A_PtrSize * 3)
;        aModuleInfo := []
        r:=DllCall(&quot;psapi\GetModuleInformation&quot;
                    , &quot;Ptr&quot;, this.hProcess
                    , &quot;Ptr&quot;, hModule
                    , &quot;Ptr&quot;, MODULEINFO.Ptr
                    , &quot;UInt&quot;, A_PtrSize * 3)
        aModuleInfo := {  lpBaseOfDll: numget(MODULEINFO, 0, &quot;Ptr&quot;)
                       ,  SizeOfImage: numget(MODULEINFO, A_PtrSize, &quot;UInt&quot;)
                       ,   EntryPoint: numget(MODULEINFO, A_PtrSize * 2, &quot;Ptr&quot;) }
        return r
    }
}
</code></pre></div><p><a href="https://forum.script-coding.com/viewtopic.php?pid=162036#p162036">Тема для обсуждения.</a></p><p><a href="https://forum.script-coding.com/viewtopic.php?id=12662">Версия для AHK v1.</a></p>]]></summary>
			<author>
				<name><![CDATA[Alectric]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=26027</uri>
			</author>
			<updated>2025-03-04T13:16:34Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?id=18387&amp;action=new</id>
		</entry>
		<entry>
			<title type="html"><![CDATA[AHK v2: ObjDump / ObjLoad]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?id=18380&amp;action=new" />
			<summary type="html"><![CDATA[<p>Функция сохранения объекта в файл или память в бинарном (RAW) виде, для последующего восстановления (загрузки) в переменную.</p><div class="codebox"><pre><code>
/* example; comment this line to try
o:={}
o.var:=1234567890
o.val:=&quot;test object property&quot;
o.arr:=[{},{}]
o.arr[1].par:=&quot;asdf&quot;
o.arr[1].val:=1234
o.arr[2].par:=&quot;jkl;&quot;
o.arr[2].val:=5678
o.m:=map()
o.m.var:=123.0
o.m.val:=&quot;qwer&quot;
o.m[11]:={}
o.m[11].par:=&quot;asdf&quot;
o.m[11].val:=1234
o.m[22]:={}
o.m[22].par:=&quot;jkl;&quot;
o.m[22].vasl:=5678
o.b:=Buffer(3,255)

SaveFileName:=a_desktop &quot;\Dumped Object.test&quot;
ObjDump(SaveFileName,o)
MyObj:=ObjLoad(SaveFileName)

msgbox MyObj.var
msgbox MyObj.val
msgbox MyObj.arr[1].par
msgbox MyObj.arr[1].val
msgbox MyObj.arr[2].par
msgbox MyObj.arr[2].val
msgbox MyObj.m.var
msgbox MyObj.m.val
msgbox MyObj.m[11].par
msgbox MyObj.m[11].val
msgbox MyObj.m[22].par
msgbox MyObj.m[22].vasl
msgbox NumGet(o.b,2,&quot;UChar&quot;)
exitapp
esc::exitapp
/*
*/
ObjLoad(addr,&amp;offset:=0) ; load dumped object to variable; from memory (addr is int) or from file (addr is string)
{
  ; #requires AutoHotkey v2.0.19
  If !IsNumber(addr)
  {
    If !FileExist(addr)
      return
    sz:=FileGetSize(addr)
    If !sz
      return
    Data:=FileRead(addr,&quot;RAW&quot;)
    addr:=Data.Ptr
    sz:=NumGet(addr,&quot;Int64&quot;)
    addr+=8
    obj:=0
  }
  ;    Sizes          1       1       2        2     4      4       8        8        8    x     x     x      x         x
  ;    Type.Enum: Char.1 UChar.2 Short.3 UShort.4 Int.5 UInt.6 Int64.7 UInt64.8 Double.9 Str.10 [].11 {}.12 map().13 buffer.14
  static types:=[&quot;Char&quot;,&quot;UChar&quot;,&quot;Short&quot;,&quot;UShort&quot;,&quot;Int&quot;,&quot;UInt&quot;,&quot;Int64&quot;,&quot;UInt64&quot;,&quot;Double&quot;]
  static syzes:=[     2,      2,      3,       3,    5,     5,      9,       9,       9]
  typ:=NumGet(addr,offset,&quot;Char&quot;)
  if (typ&lt;10)
  {
    obj:=NumGet(addr,offset+1,types[typ])
    offset+=syzes[typ]
  }
  else if (typ=10)
  {
    size:=NumGet(addr,offset+1,&quot;Int64&quot;)
    obj:=StrGet(addr+offset+9,size)
    offset+=9+size
  }
  else if (typ=11)
  {
    obj:=[]
    size:=NumGet(addr,offset+1,&quot;Int64&quot;)
    obj.Length:=size
    offset+=9
    loop size
      obj[a_index]:=ObjLoad(addr,&amp;offset)
  }
  else if (typ=12)
  {
    obj:={}
    Props:=NumGet(addr,offset+1,&quot;Int64&quot;)
    offset+=9
    loop Props
    {
      k:=ObjLoad(addr,&amp;offset)
      obj.%k%:=ObjLoad(addr,&amp;offset)
    }
  }
  else if (typ=13)
  {
    obj:=Map()
    Props:=NumGet(addr,offset+1,&quot;Int64&quot;)
    Items:=NumGet(addr,offset+9,&quot;Int64&quot;)
    offset+=17
    loop Props
    {
      k:=ObjLoad(addr,&amp;offset)
      obj.%k%:=ObjLoad(addr,&amp;offset)
    }
    loop Items
    {
      i:=ObjLoad(addr,&amp;offset)
      obj[i]:=ObjLoad(addr,&amp;offset)
    }
  }
  else if (typ=14)
  {
    size:=NumGet(addr,offset+1,&quot;Int64&quot;)
    obj:=Buffer(size)
    offset+=9
    loop size
      NumPut(&quot;UChar&quot;,NumGet(addr,offset+a_index-1,&quot;UChar&quot;),obj,a_index-1)
    offset+=size
  }
  return obj
}

ObjDump(Path,obj) ; dump object to file
{
  ; #requires AutoHotkey v2.0.19
  if !sz:=RawObjectSize(obj)
    return
  If FileExist(Path)
    FileDelete Path
  sz+=8
  Data:=Buffer(sz,0)
  ptr:=Data.Ptr
  NumPut(&quot;Int64&quot;,sz-8,ptr) ;size of all data
  RawObject(obj,ptr+8) ; use this for dumping to memory instead of ObjDump
  f:=FileOpen(Path,&quot;rw-rwd&quot;,&quot;CP0&quot;)
  Loop (sz//65536)
    f.RawWrite(ptr,65536),ptr+=65536
  f.RawWrite(ptr,Mod(sz,65536))
  f.Close()
  return sz
}
RawObject(obj,ptr,offset:=0) ; dump object to memory
{
  ; #requires AutoHotkey v2.0.19
  ;    Sizes          1       1       2        2     4      4       8        8        8    x     x     x      x         x
  ;    Type.Enum: Char.1 UChar.2 Short.3 UShort.4 Int.5 UInt.6 Int64.7 UInt64.8 Double.9 Str.10 [].11 {}.12 map().13 buffer.14
  if isObject(Obj)
  {
    if (Obj.base=[].base)
    {
      NumPut(&quot;Char&quot;,11,ptr,offset) ; type
      NumPut(&quot;Int64&quot;,Obj.Length,ptr,offset+1) ; Length of array
      offset+=9
      loop Obj.Length
        offset:=RawObject(Obj[a_index],ptr,offset)
    }
    else if (Obj.base={}.base)
    {
      NumPut(&quot;Char&quot;,12,ptr,offset) ; type
      NumPut(&quot;Int64&quot;,ObjOwnPropCount(Obj),ptr,offset+1) ; num of Props
      offset+=9
      for k,v in Obj.OwnProps()
      {
        offset:=RawObject(k,ptr,offset) ; name of prop (str)
        offset:=RawObject(v,ptr,offset) ; value of prop (any)
      }
    }
    else if (Obj.base=Map().base)
    {
      NumPut(&quot;Char&quot;,13,ptr,offset) ; type
      NumPut(&quot;Int64&quot;,ObjOwnPropCount(Obj),ptr,offset+1) ; num of Props
      NumPut(&quot;Int64&quot;,Obj.Count,ptr,offset+9) ; num of items
      offset+=17
      for k,v in Obj.OwnProps()
      {
        offset:=RawObject(k,ptr,offset) ; name of prop (str)
        offset:=RawObject(v,ptr,offset) ; value of prop (any)
      }
      for k,v in Obj
      {
        offset:=RawObject(k,ptr,offset) ; index of item (any)
        offset:=RawObject(v,ptr,offset) ; value of item (any)
      }
    }
    else if (Obj.base=Buffer().base)
    {
      NumPut(&quot;Char&quot;,14,ptr,offset) ; type
      NumPut(&quot;Int64&quot;,Obj.size,ptr,offset+1) ; num of bytes
      offset+=9
      loop Obj.size
        NumPut(&quot;UChar&quot;,NumGet(Obj,a_index-1,&quot;UChar&quot;),ptr,offset+a_index-1) ; Buffer content (RAW)
      offset+=Obj.size
    }
  }
  else if IsNumber(obj) ; num
  {
    NumPut(&quot;Char&quot;,IsFloat(Obj)?9:Obj&gt;4294967295?8       :Obj&gt;65535?6     :Obj&gt;255?4       :Obj&gt;-1?2      :Obj&gt;-129?1     :Obj&gt;-32769?3      :Obj&gt;-2147483649?5    :7,ptr,offset+0)
    NumPut(IsFloat(Obj)?&quot;Double&quot;:Obj&gt;4294967295?&quot;UInt64&quot;:Obj&gt;65535?&quot;UInt&quot;:Obj&gt;255?&quot;UShort&quot;:Obj&gt;-1?&quot;UChar&quot;:Obj&gt;-129?&quot;Char&quot;:Obj&gt;-32769?&quot;Short&quot;:Obj&gt;-2147483649?&quot;Int&quot;:&quot;Int64&quot;,Obj,ptr,offset+1)
    offset+=       IsFloat(Obj)||Obj&gt;4294967295?9       :Obj&gt;65535?5     :Obj&gt;255?3                      :Obj&gt;-129?2     :Obj&gt;-32769?3      :Obj&gt;-2147483649?5    :9
  }
  else ; string
  {
    NumPut(&quot;Char&quot;,10,ptr,offset)  ; type
    NumPut(&quot;Int64&quot;,sz:=StrPut(Obj,ptr+offset+9),ptr,offset+1) ; (str size) string text
    offset+=sz+9
  }
  return offset
}

RawObjectSize(Obj,sz:=0) ; get object&#039;s size in dumped condition
{
  ; #requires AutoHotkey v2.0.19
  if isObject(Obj)
  {
    if (Obj.base=[].base)
    {
      sz+=9 ; 1 (type) + 8 (num of items)
      loop Obj.Length
        sz:=RawObjectSize(Obj[a_index],sz)
    }
    else if (Obj.base={}.base)
    {
      sz+=9 ; 1 (type) + 8 (num of prop)
      for k,v in Obj.OwnProps()
      {
        sz:=RawObjectSize(k,sz) ; name of prop (str)
        sz:=RawObjectSize(v,sz) ; value of prop (any)
      }
    }
    else if (Obj.base=Map().base)
    {
      sz+=17 ; 1 (type) + 8 (size of Props) + 8 (size of array)
      for k,v in Obj.OwnProps()
      {
        sz:=RawObjectSize(k,sz) ; name of prop (str)
        sz:=RawObjectSize(v,sz) ; value of prop (any)
      }
      for k,v in Obj
      {
        sz:=RawObjectSize(k,sz) ; index
        sz:=RawObjectSize(v,sz) ; value
      }
    }
    else if (Obj.base=Buffer().base) ; sz = 1 (type) + 8 (size) + (buffer size)
      sz+=9+Obj.size
    else
    {
      MsgBox &quot;Unsupported object type: `&quot;&quot; Type(obj) &quot;`&quot;.&quot;,&quot;ObjDump&quot;,0x30
      return 0
    }
  }
  else if IsNumber(obj) ; sz = 1 (type) + (size of data type)
    sz+=IsFloat(Obj)||Obj&gt;4294967295?9:Obj&gt;65535?5:Obj&gt;255?3:Obj&gt;-129?2:Obj&gt;-32769?3:Obj&gt;-2147483649?5:9
  else ; sz = 1 (type) + 8 (size) + (str size)
    sz+=9+StrPut(Obj)
  return sz
}


</code></pre></div><p><a href="https://forum.script-coding.com/viewtopic.php?pid=162001#p162001">Тема для обсуждения.</a></p><p><a href="https://www.autohotkey.com/boards/viewtopic.php?t=3573">Версия для AHK v1.</a></p>]]></summary>
			<author>
				<name><![CDATA[Alectric]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=26027</uri>
			</author>
			<updated>2025-02-24T12:50:36Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?id=18380&amp;action=new</id>
		</entry>
		<entry>
			<title type="html"><![CDATA[AHK v2: Два потока]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?id=18367&amp;action=new" />
			<summary type="html"><![CDATA[<p>Делюсь методом запуска скрипта в два потока с &quot;общей&quot; памятью для обмена данными.</p><div class="codebox"><pre><code>
;#Requires AutoHotkey v2.0.19

#SingleInstance off  ; здесь просто привычный набор параметров, ничего особенного
CoordMode &quot;ToolTip&quot;
SetControlDelay -1
SetKeyDelay -1
SetWinDelay -1
SetWorkingDir A_ScriptDir
DetectHiddenWindows true
#NoTrayIcon
onexit Exit


; создаем память которая будет общей для двух потоков
DataSize:=1000
Data:=Buffer(DataSize)

; псевдо-структура (смещения в общей памяти)
_MainCycleNum:=0 ; uint = 4 byte
_SlaveCycleNum:=4 ; uint = 4 byte
_MainString:=8, MainStringSize:=12 ; string
_SlaveString:=32 ,SlaveStringSize:=50 ; string


; принимаем параметры для второго потока
SecondThread:=DataPointer:=0
if (A_Args.Length=2)
{
  SecondThread:=A_Args[1] ; PID первого потока.
  DataPointer:=A_Args[2] ; указатель на общую память.
}

if !SecondThread ; код первого потока здесь
{
  MainPID:=DllCall(&quot;GetCurrentProcessId&quot;) ; берем PID первого потока

; добавляем горячих клавиш по вкусу, через команду HotKey
  HotKey &quot;Esc&quot;,exit
; не советую добавлять их обычным методом, иначе второй поток будет тоже их обрабатывать

; запускаем второй поток и передаем ему параметры
  if a_iscompiled
  {
    run a_scriptfullpath &quot; &quot; MainPID &quot; &quot; Data.Ptr,,,&amp;SlavePID
  }
  else
  {
    run &quot;`&quot;&quot; a_ahkPath &quot;`&quot; `&quot;&quot; a_scriptfullpath &quot;`&quot; &quot; MainPID &quot; &quot; Data.Ptr,,,&amp;SlavePID
  }
  ; ---------

; запускаем контроль наличия второго потока, если его нет то завершаем скрипт
  F:=CheckPIDExist.Bind(SlavePID)
  settimer F,2000

MainLoop: ; просто метка для ориентации в коде
  loop ; основной цикл первого потока (&quot;&quot;&quot;&quot;секция автовыполнения&quot;&quot;&quot;&quot;)
  {
    sleep 500
    NumPut(&quot;uint&quot;,a_index,Data,_MainCycleNum) ; пишем номер цикла в общую память (данные для второго потока)
    tmp0:=NumGet(Data,_SlaveCycleNum,&quot;uint&quot;) ; читаем данные из общей памяти (данные от второго потока)

    tmp1:=StrGet(Data.Ptr+_SlaveString,SlaveStringSize) ; читаем строку
    StrPut(&quot;Hello World!&quot;,Data.Ptr+_MainString,MainStringSize) ; пишем строку
; выводим на экран
    tooltip &quot;Main Script`nMainPID=&quot; MainPID &quot;`nSlavePID=&quot; SlavePID &quot;`nA_Index = &quot; a_index &quot;`nData from slave:`n`t&quot; tmp0 &quot;`n`t&quot; tmp1,0,500
  }
}
else ; код второго потока здесь
{
; запускаем контроль наличия первого потока, если его нет то завершаем скрипт
  MainPID:=SecondThread
  F:=CheckPIDExist.Bind(MainPID)
  settimer F,2000

  SlavePID:=DllCall(&quot;GetCurrentProcessId&quot;)
  A_IconHidden:=false

; получаем объект для работы с памятью первого потока
  MainMemory := ClassMemory(&quot;Ahk_PID&quot; MainPID)

SlaveLoop: ; просто метка для ориентации в коде
  loop ; основной цикл второго потока (&quot;&quot;&quot;&quot;секция автовыполнения&quot;&quot;&quot;&quot;)
  {
    sleep 100
    MainMemory.readRaw(DataPointer,Data,DataSize) ; читаем общую память в переменную &quot;Data&quot;
    MainMemory.write(DataPointer+_SlaveCycleNum,A_Index) ; пишем номер цикла в общую память (данные для первого потока)
    tmp0:=numget(Data,_MainCycleNum,&quot;Uint&quot;) ; читаем данные из общей памяти (данные от первого потока)

    tmp1:=StrGet(Data.Ptr+_MainString,MainStringSize) ; читаем строку

    str:=buffer(SlaveStringSize) ; пишем строку
    StrPut(&quot;Hello Yourself!&quot;,str.Ptr,SlaveStringSize) ; пишем строку
    MainMemory.writeRaw(DataPointer+_SlaveString,str,SlaveStringSize) ; пишем строку

; выводим на экран
    tooltip &quot;Slave Script`nMainPID=&quot; MainPID &quot;`nSlavePID=&quot; SlavePID &quot;`nA_Index = &quot; a_index &quot;`nData from main:`n`t&quot; tmp0 &quot;`n`t&quot; tmp1,200,500
  }
}


CheckPIDExist(PID)
{
  if !ProcessExist(PID)
    Exit()
}

exit(*)
{
  exitapp
}

; в этом месте можно добавлять метки перехода для интерфейса и т.д.




; библиотека

; Переделанный и урезанный basic memory class by RHCP:
; https://github.com/Kalamity/classMemory
class ClassMemory
{
    ; List of useful accessible values. Some of these inherited values (the non objects) are set when the new operator is used.

    static aTypeSize := {    UChar:    1,  Char:     1
                    ,   UShort:   2,  Short:    2
                    ,   UInt:     4,  Int:      4
                    ,   UFloat:   4,  Float:    4
                    ,   Int64:    8,  Double:   8}
    static aRights := {  PROCESS_ALL_ACCESS: 0x001F0FFF
                ,   PROCESS_CREATE_PROCESS: 0x0080
                ,   PROCESS_CREATE_THREAD: 0x0002
                ,   PROCESS_DUP_HANDLE: 0x0040
                ,   PROCESS_QUERY_INFORMATION: 0x0400
                ,   PROCESS_QUERY_LIMITED_INFORMATION: 0x1000
                ,   PROCESS_SET_INFORMATION: 0x0200
                ,   PROCESS_SET_QUOTA: 0x0100
                ,   PROCESS_SUSPEND_RESUME: 0x0800
                ,   PROCESS_TERMINATE: 0x0001
                ,   PROCESS_VM_OPERATION: 0x0008
                ,   PROCESS_VM_READ: 0x0010
                ,   PROCESS_VM_WRITE: 0x0020
                ,   SYNCHRONIZE: 0x00100000}

    ; Method:    __new(program, dwDesiredAccess := &quot;&quot;, byRef handle := &quot;&quot;, windowMatchMode := 3)
    ; Example:  derivedObject := new _ClassMemory(&quot;ahk_exe calc.exe&quot;)
    ;           This is the first method which should be called when trying to access a program&#039;s memory.
    ;           If the process is successfully opened, an object is returned which can be used to read that processes memory space.
    ;           [derivedObject].hProcess stores the opened handle.
    ;           If the target process closes and re-opens, simply free the derived object and use the new operator again to open a new handle.
    ; Parameters:
    ;   program             The program to be opened. This can be any AHK windowTitle identifier, such as
    ;                       ahk_exe, ahk_class, ahk_pid, or simply the window title. e.g. &quot;ahk_exe calc.exe&quot; or &quot;Calculator&quot;.
    ;                       It&#039;s safer not to use the window title, as some things can have the same window title e.g. an open folder called &quot;Starcraft II&quot;
    ;                       would have the same window title as the game itself.
    ;                       *&#039;DetectHiddenWindows, On&#039; is required for hidden windows*
    ;   dwDesiredAccess     The access rights requested when opening the process.
    ;                       If this parameter is null the process will be opened with the following rights
    ;                       PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_READ, PROCESS_VM_WRITE, &amp; SYNCHRONIZE
    ;                       This access level is sufficient to allow all of the methods in this class to work.
    ;                       Specific process access rights are listed here http://msdn.microsoft.com/en-us/library/windows/desktop/ms684880(v=vs.85).aspx
    ;   handle (Output)     Optional variable in which a copy of the opened processes handle will be stored.
    ;                       Values:
    ;                           Null    OpenProcess failed. The script may need to be run with admin rights admin,
    ;                                   and/or with the use of _ClassMemory.setSeDebugPrivilege(). Consult A_LastError for more information.
    ;                           0       The program isn&#039;t running (not found) or you passed an incorrect program identifier parameter.
    ;                                   In some cases _ClassMemory.setSeDebugPrivilege() may be required.
    ;                           Positive Integer    A handle to the process. (Success)
    ;   windowMatchMode -   Determines the matching mode used when finding the program (windowTitle).
    ;                       The default value is 3 i.e. an exact match. Refer to AHK&#039;s setTitleMathMode for more information.
    ; Return Values:
    ;   Object  On success an object is returned which can be used to read the processes memory.
    ;   Null    Failure. A_LastError and the optional handle parameter can be consulted for more information.


    __new(program, dwDesiredAccess := &quot;&quot;, handle := &quot;&quot;, windowMatchMode := 3)
    {
        DHW:=A_DetectHiddenWindows
        DetectHiddenWindows true
        if this.PID := handle := this.findPID(program, windowMatchMode) ; set handle to 0 if program not found
        {
            ; This default access level is sufficient to read and write memory addresses, and to perform pattern scans.
            ; if the program is run using admin privileges, then this script will also need admin privileges
            if !IsInteger(dwDesiredAccess)
                dwDesiredAccess := ClassMemory.aRights.PROCESS_QUERY_INFORMATION | ClassMemory.aRights.PROCESS_VM_OPERATION | ClassMemory.aRights.PROCESS_VM_READ | ClassMemory.aRights.PROCESS_VM_WRITE
            dwDesiredAccess |= ClassMemory.aRights.SYNCHRONIZE ; add SYNCHRONIZE to all handles to allow isHandleValid() to work

            if this.hProcess := handle := this.OpenProcess(this.PID, dwDesiredAccess) ; NULL/Blank if failed to open process for some reason
            {
                this.pNumberOfBytesRead := DllCall(&quot;GlobalAlloc&quot;, &quot;UInt&quot;, 0x0040, &quot;Ptr&quot;, A_PtrSize, &quot;Ptr&quot;) ; 0x0040 initialise to 0
                this.pNumberOfBytesWritten := DllCall(&quot;GlobalAlloc&quot;, &quot;UInt&quot;, 0x0040, &quot;Ptr&quot;, A_PtrSize, &quot;Ptr&quot;) ; initialise to 0

                this.readStringLastError := False
                this.currentProgram := program
                if this.isTarget64bit := this.isTargetProcess64Bit(this.PID, this.hProcess, dwDesiredAccess)
                    this.ptrType := &quot;Int64&quot;
                else this.ptrType := &quot;UInt&quot; ; If false or Null (fails) assume 32bit

                ; if script is 64 bit, getModuleBaseAddress() should always work
                ; if target app is truly 32 bit, then getModuleBaseAddress()
                ; will work when script is 32 bit
                if (A_PtrSize != 4 || !this.isTarget64bit)
                    this.BaseAddress := this.getModuleBaseAddress()

                ; If the above failed or wasn&#039;t called, fall back to alternate method
                if this.BaseAddress &lt; 0 || !this.BaseAddress
                    this.BaseAddress := this.getProcessBaseAddress(program, windowMatchMode)
                DetectHiddenWindows DHW
                return this
            }
        }
        DetectHiddenWindows DHW
        return
    }

    __delete()
    {
        this.closeHandle(this.hProcess)
        if this.pNumberOfBytesRead
            DllCall(&quot;GlobalFree&quot;, &quot;Ptr&quot;, this.pNumberOfBytesRead)
        if this.pNumberOfBytesWritten
            DllCall(&quot;GlobalFree&quot;, &quot;Ptr&quot;, this.pNumberOfBytesWritten)
        return
    }

    version()
    {
        return 9999.9999
    }

    findPID(program, windowMatchMode := &quot;3&quot;)
    {
        ; If user passes an AHK_PID, don&#039;t bother searching. There are cases where searching windows for PIDs
        ; wont work - console apps
        if RegExMatch(program, &quot;i)\s*AHK_PID\s+(0x[[:xdigit:]]+|\d+)&quot;, &amp;pid)
            return pid[1]
        if windowMatchMode
        {
            ; This is a string and will not contain the 0x prefix
            mode := A_TitleMatchMode
            ; remove hex prefix as SetTitleMatchMode will throw a run time error. This will occur if integer mode is set to hex and user passed an int (unquoted)
            windowMatchMode:=StrReplace(windowMatchMode,&quot;0x&quot;)
;            StringReplace, windowMatchMode, windowMatchMode, 0x
            SetTitleMatchMode windowMatchMode
        }
        pid:=WinGetPid(program)
        if windowMatchMode
            SetTitleMatchMode mode    ; In case executed in autoexec

        ; If use &#039;ahk_exe test.exe&#039; and winget fails (which can happen when setSeDebugPrivilege is required),
        ; try using the process command. When it fails due to setSeDebugPrivilege, setSeDebugPrivilege will still be required to openProcess
        ; This should also work for apps without windows.
        if (!pid &amp;&amp; RegExMatch(program, &quot;i)\bAHK_EXE\b\s*(.*)&quot;, &amp;fileNm))
        {
            ; remove any trailing AHK_XXX arguments
            filename := RegExReplace(fileNm[1], &quot;i)\bahk_(class|id|pid|group)\b.*&quot;, &quot;&quot;)
            filename := trim(filename)    ; extra spaces will make process command fail
            ; AHK_EXE can be the full path, so just get filename
            SplitPath fileName , &amp;fileName
            if (fileName) ; if filename blank, scripts own pid is returned
                pid := ProcessExist(fileName)
        }

        return pid ? pid : 0 ; PID is null on fail, return 0
    }

    ; Method:   isHandleValid()
    ;           This method provides a means to check if the internal process handle is still valid
    ;           or in other words, the specific target application instance (which you have been reading from)
    ;           has closed or restarted.
    ;           For example, if the target application closes or restarts the handle will become invalid
    ;           and subsequent calls to this method will return false.
    ;
    ; Return Values:
    ;   True    The handle is valid.
    ;   False   The handle is not valid.
    ;
    ; Notes:
    ;   This operation requires a handle with SYNCHRONIZE access rights.
    ;   All handles, even user specified ones are opened with the SYNCHRONIZE access right.

    isHandleValid()
    {
        return 0x102 = DllCall(&quot;WaitForSingleObject&quot;, &quot;Ptr&quot;, this.hProcess, &quot;UInt&quot;, 0)
        ; WaitForSingleObject return values
        ; -1 if called with null hProcess (sets lastError to 6 - invalid handle)
        ; 258 / 0x102 WAIT_TIMEOUT - if handle is valid (process still running)
        ; 0  WAIT_OBJECT_0 - if process has terminated
    }

    ; Method:   openProcess(PID, dwDesiredAccess)
    ;           ***Note:    This is an internal method which shouldn&#039;t be called directly unless you absolutely know what you are doing.
    ;                       This is because the new operator, in addition to calling this method also sets other values
    ;                       which are required for the other methods to work correctly.
    ; Parameters:
    ;   PID                 The Process ID of the target process.
    ;   dwDesiredAccess     The access rights requested when opening the process.
    ;                       Specific process access rights are listed here http://msdn.microsoft.com/en-us/library/windows/desktop/ms684880(v=vs.85).aspx
    ; Return Values:
    ;   Null/blank          OpenProcess failed. If the target process has admin rights, then the script also needs to be ran as admin.
    ;                       _ClassMemory.setSeDebugPrivilege() may also be required.
    ;   Positive integer    A handle to the process.

    openProcess(PID, dwDesiredAccess)
    {
        r := DllCall(&quot;OpenProcess&quot;, &quot;UInt&quot;, dwDesiredAccess, &quot;Int&quot;, False, &quot;UInt&quot;, PID, &quot;Ptr&quot;)
        ; if it fails with 0x5 ERROR_ACCESS_DENIED, try enabling privilege ... lots of users never try this.
        ; there may be other errors which also require DebugPrivilege....
        if (!r &amp;&amp; A_LastError = 5)
        {
            this.setSeDebugPrivilege(true) ; no harm in enabling it if it is already enabled by user
            if (r2 := DllCall(&quot;OpenProcess&quot;, &quot;UInt&quot;, dwDesiredAccess, &quot;Int&quot;, False, &quot;UInt&quot;, PID, &quot;Ptr&quot;))
                return r2
            DllCall(&quot;SetLastError&quot;, &quot;UInt&quot;, 5) ; restore original error if it doesnt work
        }
        ; If fails with 0x5 ERROR_ACCESS_DENIED (when setSeDebugPrivilege() is req.), the func. returns 0 rather than null!! Set it to null.
        ; If fails for another reason, then it is null.
        return r ? r : 0
    }

    ; Method:   closeHandle(hProcess)
    ;           Note:   This is an internal method which is automatically called when the script exits or the derived object is freed/destroyed.
    ;                   There is no need to call this method directly. If you wish to close the handle simply free the derived object.
    ;                   i.e. derivedObject := [] ; or derivedObject := &quot;&quot;
    ; Parameters:
    ;   hProcess        The handle to the process, as returned by openProcess().
    ; Return Values:
    ;   Non-Zero        Success
    ;   0               Failure

    closeHandle(hProcess)
    {
        return DllCall(&quot;CloseHandle&quot;, &quot;Ptr&quot;, hProcess)
    }

    ; Methods:      numberOfBytesRead() / numberOfBytesWritten()
    ;               Returns the number of bytes read or written by the last ReadProcessMemory or WriteProcessMemory operation.
    ;
    ; Return Values:
    ;   zero or positive value      Number of bytes read/written
    ;   -1                          Failure. Shouldn&#039;t occur

    numberOfBytesRead()
    {
        return !this.pNumberOfBytesRead ? -1 : NumGet(this.pNumberOfBytesRead+0, &quot;Ptr&quot;)
    }
    numberOfBytesWritten()
    {
        return !this.pNumberOfBytesWritten ? -1 : NumGet(this.pNumberOfBytesWritten+0, &quot;Ptr&quot;)
    }


    ; Method:   read(address, type := &quot;UInt&quot;, aOffsets*)
    ;           Reads various integer type values
    ; Parameters:
    ;       address -   The memory address of the value or if using the offset parameter,
    ;                   the base address of the pointer.
    ;       type    -   The integer type.
    ;                   Valid types are UChar, Char, UShort, Short, UInt, Int, Float, Int64 and Double.
    ;                   Note: Types must not contain spaces i.e. &quot; UInt&quot; or &quot;UInt &quot; will not work.
    ;                   When an invalid type is passed the method returns NULL and sets ErrorLevel to -2
    ;       aOffsets* - A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
    ;                   The address (base address) and offsets should point to the memory address which holds the integer.
    ; Return Values:
    ;       integer -   Indicates success.
    ;       Null    -   Indicates failure. Check ErrorLevel and A_LastError for more information.
    ;       Note:       Since the returned integer value may be 0, to check for success/failure compare the result
    ;                   against null i.e. if (result = &quot;&quot;) then an error has occurred.
    ;                   When reading doubles, adjusting &quot;SetFormat, float, totalWidth.DecimalPlaces&quot;
    ;                   may be required depending on your requirements.

    read(address, type := &quot;UInt&quot;)
    {
        ; If invalid type RPM() returns success (as bytes to read resolves to null in dllCall())
        ; so set errorlevel to invalid parameter for DLLCall() i.e. -2
        if !ClassMemory.aTypeSize.%type%
            return false
        result:=0
        if DllCall(&quot;ReadProcessMemory&quot;, &quot;Ptr&quot;, this.hProcess, &quot;Ptr&quot;, address, type &quot;*&quot;, &amp;result, &quot;Ptr&quot;, ClassMemory.aTypeSize.%type%, &quot;Ptr&quot;, this.pNumberOfBytesRead)
            return result
        return
    }

    ; Method:   readRaw(address, byRef buffer, bytes := 4, aOffsets*)
    ;           Reads an area of the processes memory and stores it in the buffer variable
    ; Parameters:
    ;       address  -  The memory address of the area to read or if using the offsets parameter
    ;                   the base address of the pointer which points to the memory region.
    ;       buffer   -  The unquoted variable name for the buffer. This variable will receive the contents from the address space.
    ;                   This method calls varsetCapcity() to ensure the variable has an adequate size to perform the operation.
    ;                   If the variable already has a larger capacity (from a previous call to varsetcapcity()), then it will not be shrunk.
    ;                   Therefore it is the callers responsibility to ensure that any subsequent actions performed on the buffer variable
    ;                   do not exceed the bytes which have been read - as these remaining bytes could contain anything.
    ;       bytes   -   The number of bytes to be read.
    ;       aOffsets* - A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
    ;                   The address (base address) and offsets should point to the memory address which is to be read
    ; Return Values:
    ;       Non Zero -   Indicates success.
    ;       Zero     -   Indicates failure. Check errorLevel and A_LastError for more information
    ;
    ; Notes:            The contents of the buffer may then be retrieved using AHK&#039;s NumGet() and StrGet() functions.
    ;                   This method offers significant (~30% and up) performance boost when reading large areas of memory.
    ;                   As calling ReadProcessMemory for four bytes takes a similar amount of time as it does for 1,000 bytes.

    readRaw(address, buffer, bytes := 4)
    {
;        Data.__new(bytes)
        return DllCall(&quot;ReadProcessMemory&quot;, &quot;Ptr&quot;, this.hProcess, &quot;Ptr&quot;, address, &quot;Ptr&quot;, IsInteger(Buffer)?Buffer:buffer.ptr, &quot;Ptr&quot;, bytes, &quot;Ptr&quot;, this.pNumberOfBytesRead)
    }

    ; Method:   write(address, value, type := &quot;Uint&quot;, aOffsets*)
    ;           Writes various integer type values to the process.
    ; Parameters:
    ;       address -   The memory address to which data will be written or if using the offset parameter,
    ;                   the base address of the pointer.
    ;       type    -   The integer type.
    ;                   Valid types are UChar, Char, UShort, Short, UInt, Int, Float, Int64 and Double.
    ;                   Note: Types must not contain spaces i.e. &quot; UInt&quot; or &quot;UInt &quot; will not work.
    ;                   When an invalid type is passed the method returns NULL and sets ErrorLevel to -2
    ;       aOffsets* - A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
    ;                   The address (base address) and offsets should point to the memory address which is to be written to.
    ; Return Values:
    ;       Non Zero -  Indicates success.
    ;       Zero     -  Indicates failure. Check errorLevel and A_LastError for more information
    ;       Null    -   An invalid type was passed. Errorlevel is set to -2

    write(address, value, type := &quot;Uint&quot;)
    {
        if !ClassMemory.aTypeSize.%type%
            return false
        return DllCall(&quot;WriteProcessMemory&quot;, &quot;Ptr&quot;, this.hProcess, &quot;Ptr&quot;, address, type &quot;*&quot;, &amp;value, &quot;Ptr&quot;, ClassMemory.aTypeSize.%type%, &quot;Ptr&quot;, this.pNumberOfBytesWritten)
    }

    ; Method:   writeRaw(address, pBuffer, sizeBytes, aOffsets*)
    ;           Writes a buffer to the process.
    ; Parameters:
    ;   address -       The memory address to which the contents of the buffer will be written
    ;                   or if using the offset parameter, the base address of the pointer.
    ;   pBuffer -       A pointer to the buffer which is to be written.
    ;                   This does not necessarily have to be the beginning of the buffer itself e.g. pBuffer := &amp;buffer + offset
    ;   sizeBytes -     The number of bytes which are to be written from the buffer.
    ;   aOffsets* -     A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
    ;                   The address (base address) and offsets should point to the memory address which is to be written to.
    ; Return Values:
    ;       Non Zero -  Indicates success.
    ;       Zero     -  Indicates failure. Check errorLevel and A_LastError for more information

    writeRaw(address, Buffer, sizeBytes)
    {
        return DllCall(&quot;WriteProcessMemory&quot;, &quot;Ptr&quot;, this.hProcess, &quot;Ptr&quot;, address, &quot;Ptr&quot;, IsInteger(Buffer)?Buffer:Buffer.Ptr, &quot;Ptr&quot;, sizeBytes, &quot;Ptr&quot;, this.pNumberOfBytesWritten)
    }

    ; Interesting note:
    ; Although handles are 64-bit pointers, only the less significant 32 bits are employed in them for the purpose
    ; of better compatibility (for example, to enable 32-bit and 64-bit processes interact with each other)
    ; Here are examples of such types: HANDLE, HWND, HMENU, HPALETTE, HBITMAP, etc.
    ; http://www.viva64.com/en/k/0005/



    ; Method:   getProcessBaseAddress(WindowTitle, windowMatchMode := 3)
    ;           Returns the base address of a process. In most cases this will provide the same result as calling getModuleBaseAddress() (when passing
    ;           a null value as the module parameter), however getProcessBaseAddress() will usually work regardless of the bitness
    ;           of both the AHK exe and the target process.
    ;           *This method relies on the target process having a window and will not work for console apps*
    ;           *&#039;DetectHiddenWindows, On&#039; is required for hidden windows*
    ;           ***If this returns an incorrect value, try using (the MORE RELIABLE) getModuleBaseAddress() instead.***
    ; Parameters:
    ;   windowTitle         This can be any AHK windowTitle identifier, such as
    ;                       ahk_exe, ahk_class, ahk_pid, or simply the window title. e.g. &quot;ahk_exe calc.exe&quot; or &quot;Calculator&quot;.
    ;                       It&#039;s safer not to use the window title, as some things can have the same window title e.g. an open folder called &quot;Starcraft II&quot;
    ;                       would have the same window title as the game itself.
    ;   windowMatchMode     Determines the matching mode used when finding the program&#039;s window (windowTitle).
    ;                       The default value is 3 i.e. an exact match. The current matchmode will be used if the parameter is null or 0.
    ;                       Refer to AHK&#039;s setTitleMathMode for more information.
    ; Return Values:
    ;   Positive integer    The base address of the process (success).
    ;   Null                The process&#039;s window couldn&#039;t be found.
    ;   0                   The GetWindowLong or GetWindowLongPtr call failed. Try getModuleBaseAddress() instead.


    getProcessBaseAddress(windowTitle, windowMatchMode := &quot;3&quot;)
    {
        mode:=0
        if (windowMatchMode &amp;&amp; A_TitleMatchMode != windowMatchMode)
        {
            mode := A_TitleMatchMode ; This is a string and will not contain the 0x prefix
            windowMatchMode:=StrReplace(windowMatchMode,&quot;0x&quot;)
;            StringReplace windowMatchMode, windowMatchMode, 0x ; remove hex prefix as SetTitleMatchMode will throw a run time error. This will occur if integer mode is set to hex and matchmode param is passed as an number not a string.
            SetTitleMatchMode windowMatchMode    ;mode 3 is an exact match
        }
        hWnd:=WinGetID(WindowTitle)
        if mode
            SetTitleMatchMode mode    ; In case executed in autoexec
        if !hWnd
            return 0 ; return blank failed to find window
      ; GetWindowLong returns a Long (Int) and GetWindowLongPtr return a Long_Ptr
        return DllCall(A_PtrSize = 4     ; If DLL call fails, returned value will = 0
            ? &quot;GetWindowLong&quot;
            : &quot;GetWindowLongPtr&quot;
            , &quot;Ptr&quot;, hWnd, &quot;Int&quot;, -6, A_Is64bitOS ? &quot;Int64&quot; : &quot;UInt&quot;)
            ; For the returned value when the OS is 64 bit use Int64 to prevent negative overflow when AHK is 32 bit and target process is 64bit
            ; however if the OS is 32 bit, must use UInt, otherwise the number will be huge (however it will still work as the lower 4 bytes are correct)
            ; Note - it&#039;s the OS bitness which matters here, not the scripts/AHKs
    }

    ; http://winprogger.com/getmodulefilenameex-enumprocessmodulesex-failures-in-wow64/
    ; http://stackoverflow.com/questions/3801517/how-to-enum-modules-in-a-64bit-process-from-a-32bit-wow-process

    ; Method:            getModuleBaseAddress(module := &quot;&quot;, byRef aModuleInfo := &quot;&quot;)
    ; Parameters:
    ;   moduleName -    The file name of the module/dll to find e.g. &quot;calc.exe&quot;, &quot;GDI32.dll&quot;, &quot;Bass.dll&quot; etc
    ;                   If no module (null) is specified, the address of the base module - main()/process will be returned
    ;                   e.g. for calc.exe the following two method calls are equivalent getModuleBaseAddress() and getModuleBaseAddress(&quot;calc.exe&quot;)
    ;   aModuleInfo -   (Optional) A module Info object is returned in this variable. If method fails this variable is made blank.
    ;                   This object contains the keys: name, fileName, lpBaseOfDll, SizeOfImage, and EntryPoint
    ; Return Values:
    ;   Positive integer - The module&#039;s base/load address (success).
    ;   -1 - Module not found
    ;   -3 - EnumProcessModulesEx failed
    ;   -4 - The AHK script is 32 bit and you are trying to access the modules of a 64 bit target process. Or the target process has been closed.
    ; Notes:    A 64 bit AHK can enumerate the modules of a target 64 or 32 bit process.
    ;           A 32 bit AHK can only enumerate the modules of a 32 bit process
    ;           This method requires PROCESS_QUERY_INFORMATION + PROCESS_VM_READ access rights. These are included by default with this class.

    getModuleBaseAddress()
    {
        moduleName := this.GetModuleFileNameEx(0, True) ; main executable module of the process - get just fileName no path
        aModules:=map()
        if (r := this.getModules(&amp;aModules, True) &lt; 0)
            return r ; -4, -3
        return aModules.has(moduleName) ? (aModules[moduleName].lpBaseOfDll) : -1
        ; no longer returns -5 for failed to get module info
    }

    ; SeDebugPrivileges is required to read/write memory in some programs.
    ; This only needs to be called once when the script starts,
    ; regardless of the number of programs being read (or if the target programs restart)
    ; Call this before attempting to call any other methods in this class
    ; i.e. call _ClassMemory.setSeDebugPrivilege() at the very start of the script.

    setSeDebugPrivilege(enable := True)
    {
        h := DllCall(&quot;OpenProcess&quot;, &quot;UInt&quot;, 0x0400, &quot;Int&quot;, false, &quot;UInt&quot;, DllCall(&quot;GetCurrentProcessId&quot;), &quot;Ptr&quot;)
        ; Open an adjustable access token with this process (TOKEN_ADJUST_PRIVILEGES = 32)
        t:=0
        DllCall(&quot;Advapi32.dll\OpenProcessToken&quot;, &quot;Ptr&quot;, h, &quot;UInt&quot;, 32, &quot;PtrP&quot;, &amp;t)
        ti:=Buffer(16,0)
;        VarSetCapacity(ti, 16, 0)  ; structure of privileges
        NumPut(&quot;UInt&quot;,1, ti, 0)  ; one entry in the privileges array...
        ; Retrieves the locally unique identifier of the debug privilege:
        luid:=0
        DllCall(&quot;Advapi32.dll\LookupPrivilegeValue&quot;, &quot;Ptr&quot;, 0, &quot;Str&quot;, &quot;SeDebugPrivilege&quot;, &quot;Int64P&quot;, &amp;luid)
        NumPut(&quot;Int64&quot;,luid, ti, 4)
        if enable
            NumPut(&quot;UInt&quot;,2, ti, 12)  ; enable this privilege: SE_PRIVILEGE_ENABLED = 2
        ; Update the privileges of this process with the new access token:
        r := DllCall(&quot;Advapi32.dll\AdjustTokenPrivileges&quot;, &quot;Ptr&quot;, t, &quot;Int&quot;, false, &quot;Ptr&quot;, ti.Ptr, &quot;UInt&quot;, 0, &quot;Ptr&quot;, 0, &quot;Ptr&quot;, 0)
        DllCall(&quot;CloseHandle&quot;, &quot;Ptr&quot;, t)  ; close this access token handle to save memory
        DllCall(&quot;CloseHandle&quot;, &quot;Ptr&quot;, h)  ; close this process handle to save memory
        return r
    }


    ; Method:  isTargetProcess64Bit(PID, hProcess := &quot;&quot;, currentHandleAccess := &quot;&quot;)
    ;          Determines if a process is 64 bit.
    ; Parameters:
    ;   PID                     The Process ID of the target process. If required this is used to open a temporary process handle.
    ;   hProcess                (Optional) A handle to the process, as returned by openProcess() i.e. [derivedObject].hProcess
    ;   currentHandleAccess     (Optional) The dwDesiredAccess value used when opening the process handle which has been
    ;                           passed as the hProcess parameter. If specifying hProcess, you should also specify this value.
    ; Return Values:
    ;   True    The target application is 64 bit.
    ;   False   The target application is 32 bit.
    ;   Null    The method failed.
    ; Notes:
    ;   This is an internal method which is called when the new operator is used. It is used to set the pointer type for 32/64 bit applications so the pointer methods will work.
    ;   This operation requires a handle with PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION access rights.
    ;   If the currentHandleAccess parameter does not contain these rights (or not passed) or if the hProcess (process handle) is invalid (or not passed)
    ;   a temporary handle is opened to perform this operation. Otherwise if hProcess and currentHandleAccess appear valid
    ;   the passed hProcess is used to perform the operation.

    isTargetProcess64Bit(PID, hProcess := 0, currentHandleAccess := 0)
    {
        closeHandle:=0
        if !A_Is64bitOS
            return False
        ; If insufficient rights, open a temporary handle
        else if !hProcess || !(currentHandleAccess &amp; (ClassMemory.aRights.PROCESS_QUERY_INFORMATION | ClassMemory.aRights.PROCESS_QUERY_LIMITED_INFORMATION))
            closeHandle := hProcess := this.openProcess(PID, ClassMemory.aRights.PROCESS_QUERY_INFORMATION)
        Wow64Process:=0
        if (hProcess &amp;&amp; DllCall(&quot;IsWow64Process&quot;, &quot;Ptr&quot;, hProcess, &quot;Int*&quot;, &amp;Wow64Process))
            result := !Wow64Process
        if closeHandle
          this.CloseHandle(hProcess)
        return result
    }
    /*
        _Out_  PBOOL Wow64Proces value set to:
        True if the process is running under WOW64 - 32bit app on 64bit OS.
        False if the process is running under 32-bit Windows!
        False if the process is a 64-bit application running under 64-bit Windows.
    */

    getModules(&amp;aModules, useFileNameAsKey := False)
    {
        if (A_PtrSize = 4 &amp;&amp; this.IsTarget64bit)
            return -4 ; AHK is 32bit and target process is 64 bit, this function wont work
;        aModules := []
        lphModule:=Buffer(4)
        if !moduleCount := this.EnumProcessModulesEx(&amp;lphModule)
            return -3
        loop moduleCount
        {
            aModuleInfo:=map()
            hModule := numget(lphModule, (A_index - 1) * A_PtrSize,&quot;Ptr&quot;)
            this.GetModuleInformation(hModule, &amp;aModuleInfo)
            aModuleInfo.Name := this.GetModuleFileNameEx(hModule)
            filePath := aModuleInfo.name
            SplitPath filePath, &amp;fileName
            aModuleInfo.fileName := fileName
            if useFileNameAsKey
                aModules[fileName] := aModuleInfo
            else aModules.insert(aModuleInfo)
        }
        return moduleCount
    }

    ; lpFilename [out]
    ; A pointer to a buffer that receives the fully qualified path to the module.
    ; If the size of the file name is larger than the value of the nSize parameter, the function succeeds
    ; but the file name is truncated and null-terminated.
    ; If the buffer is adequate the string is still null terminated.

    GetModuleFileNameEx(hModule := 0, fileNameNoPath := False)
    {
        ; ANSI MAX_PATH = 260 (includes null) - unicode can be ~32K.... but no one would ever have one that size
        ; So just give it a massive size and don&#039;t bother checking. Most coders just give it MAX_PATH size anyway
        VarSetStrCapacity(&amp;lpFilename,1024)
;        VarSetCapacity(lpFilename, 2048 * (A_IsUnicode ? 2 : 1))
        DllCall(&quot;psapi\GetModuleFileNameEx&quot;
                    , &quot;Ptr&quot;, this.hProcess
                    , &quot;Ptr&quot;, hModule
                    , &quot;Str&quot;, lpFilename
                    , &quot;Uint&quot;, 1024)
        if fileNameNoPath
            SplitPath lpFilename, &amp;lpFilename ; strips the path so = GDI32.dll

        return lpFilename
    }



    ; dwFilterFlag
    ;   LIST_MODULES_DEFAULT    0x0
    ;   LIST_MODULES_32BIT      0x01
    ;   LIST_MODULES_64BIT      0x02
    ;   LIST_MODULES_ALL        0x03
    ; If the function is called by a 32-bit application running under WOW64, the dwFilterFlag option
    ; is ignored and the function provides the same results as the EnumProcessModules function.
    EnumProcessModulesEx(&amp;lphModule, dwFilterFlag := 0x03)
    {
;        lastError := A_LastError
        size := 4 ; VarSetCapacity(lphModule, 4)
        reqSize:=0
        loop
        {
            if !DllCall(&quot;psapi\EnumProcessModulesEx&quot;
                        , &quot;Ptr&quot;, this.hProcess
                        , &quot;Ptr&quot;, lphModule.Ptr
                        , &quot;Uint&quot;, size
                        , &quot;Uint*&quot;, &amp;reqSize
                        , &quot;Uint&quot;, dwFilterFlag)
                return 0
            else if (size &gt;= reqSize)
                break
            else
            {
                size := reqSize
                lphModule.__new(size)
            }
        }
        ; On first loop it fails with A_lastError = 0x299 as its meant to
        ; might as well reset it to its previous version
;        DllCall(&quot;SetLastError&quot;, &quot;UInt&quot;, lastError)
        return reqSize // A_PtrSize ; module count  ; sizeof(HMODULE) - enumerate the array of HMODULEs
    }

    GetModuleInformation(hModule,&amp;aModuleInfo)
    {
        MODULEINFO:=buffer(A_PtrSize * 3)
;        VarSetCapacity(MODULEINFO, A_PtrSize * 3)
;        aModuleInfo := []
        r:=DllCall(&quot;psapi\GetModuleInformation&quot;
                    , &quot;Ptr&quot;, this.hProcess
                    , &quot;Ptr&quot;, hModule
                    , &quot;Ptr&quot;, MODULEINFO.Ptr
                    , &quot;UInt&quot;, A_PtrSize * 3)
        aModuleInfo := {  lpBaseOfDll: numget(MODULEINFO, 0, &quot;Ptr&quot;)
                       ,  SizeOfImage: numget(MODULEINFO, A_PtrSize, &quot;UInt&quot;)
                       ,   EntryPoint: numget(MODULEINFO, A_PtrSize * 2, &quot;Ptr&quot;) }
        return r
    }
}

</code></pre></div><p>Предупреждение!<br />Есть вероятность недочетов при переводе с AHK v1. <a href="https://forum.script-coding.com/viewtopic.php?id=15689">Версия для AHK v1</a>.<br /><a href="https://forum.script-coding.com/viewtopic.php?id=18368">Тема для обсуждения</a></p>]]></summary>
			<author>
				<name><![CDATA[Alectric]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=26027</uri>
			</author>
			<updated>2025-02-12T17:11:35Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?id=18367&amp;action=new</id>
		</entry>
		<entry>
			<title type="html"><![CDATA[AHK: Альтернатива стандартному Tooltip]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?id=18258&amp;action=new" />
			<summary type="html"><![CDATA[<p>Возможности альтернативного Tooltip:<br /></p><ul><li><p>Включение прозрачности, в том числе прозрачность для кликов мышью;</p></li><li><p>Автоматическое пропадание после указанного времени;</p></li><li><p>Пропадание при нажатии;</p></li><li><p>Изменение параметров шрифта;</p></li><li><p>Изменение цвета фона;</p></li><li><p>Уменьшенное мерцание при частом обновлении текста;</p></li><li><p>Работа с несколькими мониторами.</p></li></ul><p><a href="https://forum.script-coding.com/viewtopic.php?id=18259">Тема для обсуждения.</a></p><p>Пример:<br /></p><div class="codebox"><pre><code>SetBatchLines,-1

tooltip1:=new Tool_Tip()
tooltip1.Show(&quot;Это тестовый текст`n`nНажмите F1 чтобы переключить прозрачность&quot;,,,10000)

tooltip2:=new Tool_Tip()
loop
{
  mousegetpos,mX,mY,mW,mC
  text:=&quot;Альтернативный Tooltip`n&quot; a_tickcount &quot;`nmX=&quot; mX &quot;`nmY=&quot; mY &quot;`nmW=&quot; mW &quot;`nmC=&quot; mC
  tooltip2.Show(text,,,,Trans,0x303030,&quot;c00FF00 s12 w600 italic underline&quot;,&quot;Times New Roman&quot;)
;  tooltip2.Show(&quot;Static text`n`n`n`n`nCollision test&quot;,,,2000,Trans)
  sleep,30
}
return

esc::
  exitapp

f1::
  Trans:=!Trans
  mousegetpos,mx,my,mw,mc
  tooltip1.Show(Trans?&quot;Прозрачность включена`nклики проходят насквозь&quot;:&quot;Прозрачность выключена`nнажмите сюда чтобы убрать&quot;,,my-50,10000,Trans)
  return

f2::
  sleep,5000
  return

; Ctrl+Alt+L
^!L::listlines
  return
</code></pre></div><p>Class:<br /></p><div class="codebox"><pre><code>; Метод Show(Text,X,Y,Delay,Transparent,BackColor,Font,FontName)
; Text		- Текст который необходимо отобразить
; X, Y		- Координаты появления относительно экрана
; Delay		- задержка пропадания после вызова в миллисекундах
; Transparent	- Прозрачность в формате AHK Gui (2-255), 0-не прозрачно, 1-прозрачность 180
; BackColor	- Цвет фона
; Font		- Параметры шрифта в формате Gui (цвет, размер, наклон, толщина, подчеркнутый, зачеркнутый)
; FontName	- Название шрифта
;
; Метод Hide() - Скрывает Tooltip
;
class Tool_Tip
{
  static DPICoef:=Tool_Tip.GetDPICoef()
  Pos[]
  {
    get
    {
      wingetpos,X,Y,W,H,% &quot;ahk_id &quot; this.hTool_Tip
      return {&quot;X&quot;:X,&quot;Y&quot;:Y,&quot;W&quot;:W,&quot;H&quot;:H}
    }
    set
    {
      return value
    }
  }
  __Delete()
  {
    obm:=this.obm
    settimer,% obm,Delete
    this.obm:=&quot;&quot;
    if this.hTool_Tip
      Gui,% this.hTool_Tip . &quot;:Destroy&quot;
  }
  __New()
  {
    this.Text:=Text
    this.Font:=&quot;s9&quot;
    this.FontName:=&quot;Arial&quot;
    this.obm:=ObjBindMethod(this,&quot;Hide&quot;)
    SaveDefaultGui:=A_DefaultGui
    Gui,New,+HwndhTool_Tip +ToolWindow -sysmenu -Resize +AlwaysOnTop -Caption +Border +E0x2080000
    Gui,% SaveDefaultGui . &quot;:Default&quot;
    this.hTool_Tip:=hTool_Tip
    Gui,%hTool_Tip%:font,% this.Font,% this.FontName
    Gui,%hTool_Tip%:Color,FFFFFF
    Gui,%hTool_Tip%:Margin,2,2
    Gui,%hTool_Tip%:add,text,+hwndhText
    this.hText:=hText
    obm:=this.obm
    GuiControl,+g,% this.hTool_Tip,% obm
    GuiControl,+g,% this.hText,% obm
  }
  Show(Text=&quot;&quot;,X=&quot;&quot;,Y=&quot;&quot;,Delay=&quot;&quot;,Trans=&quot;&quot;,BackColor=&quot;&quot;,Font=&quot;s9&quot;,FontName=&quot;Arial&quot;)
  {
    SaveCoordMode:=A_CoordModeMouse
    CoordMode,Mouse,Screen
    MouseGetPos,mX,mY,mW,mC,2
    CoordMode,Mouse,% SaveCoordMode
    if (X=&quot;&quot;)
      X:=mX+10
    if (Y=&quot;&quot;)
      Y:=mY+20
    if (Font!=this.Font)
    {
      this.Font:=Font
      hTool_Tip:=this.hTool_Tip
      Gui,%hTool_Tip%:font,% this.Font,% this.FontName
      GuiControl,Font,% this.hText
    }
    if (FontName!=this.FontName)
    {
      this.FontName:=FontName
      hTool_Tip:=this.hTool_Tip
      Gui,%hTool_Tip%:font,,% this.FontName
      GuiControl,Font,% this.hText
    }
    if (Text!=this.Text)
    {
      this.Text:=Text
      Size:=this.Measure(Text)
      GuiControl,move,% this.hText,% &quot;w&quot; Size.W &quot; h&quot; Size.H
      GuiControl,,% this.hText,% Text
      W:=Size.W+8
      H:=Size.H+8
      this.bW:=W
      this.bH:=H
      W/=Tool_Tip.DPICoef
      H/=Tool_Tip.DPICoef
    }
    B:=this.Bound(X,Y,this.bW,this.bH)
    X:=B.X
    Y:=B.Y
    if (X!=this.X or Y!=this.Y or W!=this.W or H!=this.H or BackColor!=this.BackColor or !this.Visible)
    {
      this.X:=X
      this.Y:=Y
      this.W:=W
      this.H:=H
      this.Visible:=true
      hTool_Tip:=this.hTool_Tip
      if (BackColor!=this.BackColor)
      {
        this.BackColor:=BackColor
        Gui,%hTool_Tip%:Color,% BackColor
      }
      o:=(X!=&quot;&quot;?&quot;x&quot; X &quot; &quot;:&quot;&quot;)
       . (Y!=&quot;&quot;?&quot;y&quot; Y &quot; &quot;:&quot;&quot;)
       . (W!=&quot;&quot;?&quot;w&quot; W &quot; &quot;:&quot;&quot;)
       . (H!=&quot;&quot;?&quot;h&quot; H &quot; &quot;:&quot;&quot;)
       . &quot; NoActivate&quot;
      Gui,%hTool_Tip%:show,% o
    }
    if (Trans!=this.OldTrans)
    {
      if Trans
      {
        WinSet,Transparent,% Trans=1?180:Trans,% &quot;ahk_id &quot; this.hTool_Tip
        Winset,ExStyle,+0x20,% &quot;ahk_id &quot; this.hTool_Tip
      }
      else
      {
        WinSet,Transparent,255,% &quot;ahk_id &quot; this.hTool_Tip
        Winset,ExStyle,-0x20,% &quot;ahk_id &quot; this.hTool_Tip
      }
      this.OldTrans:=Trans
    }
    if Delay
    {
      obm:=this.obm
      settimer,% obm,% -abs(Delay)
    }
  }
  Hide()
  {
    if this.hTool_Tip
      Gui,% this.hTool_Tip . &quot;:hide&quot;
    this.Visible:=false
  }
  Measure(Text)
  {
    SaveDefaultGui:=A_DefaultGui
    Gui,New,+HwndhTest_Tip
    Gui,% SaveDefaultGui . &quot;:Default&quot;
    Gui,%hTest_Tip%:font,% this.Font,% this.FontName
    Gui,%hTest_Tip%:add,text,+hwndhTest,% Text
    GuiControlGet,Size,Pos,% hTest
    Gui,%hTest_Tip%:Destroy
    return {&quot;X&quot;:SizeX,&quot;Y&quot;:SizeY,&quot;W&quot;:SizeW*Tool_Tip.DPICoef,&quot;H&quot;:SizeH*Tool_Tip.DPICoef}
  }
  GetDPICoef()
  {
    SaveDefaultGui:=A_DefaultGui
    gui,New:add,text,x0 y0 w1000 h1000 +hwndhTest
    Controlgetpos,Testx,Testy,Testw,Testh,,ahk_id %hTest%
    gui,Destroy
    Gui,% SaveDefaultGui . &quot;:Default&quot;
    return Testw/1000
  }
  Bound(X,Y,W=0,H=0)
  {
    static last:=1
    SysGet,Monitor,MonitorCount
    Loop,% Monitor
    {
      SysGet,B,Monitor,% A_Index
      if (X&gt;=BLeft and X&lt;=BRight and Y&gt;=BTop and Y&lt;=BBottom)
      {
        SysGet,B,MonitorWorkArea,% A_Index
        last:=a_index
        return {&quot;X&quot;:(X+W&gt;BRight?BRight-W:X)
               ,&quot;Y&quot;:(Y+H&gt;BBottom?BBottom-H:Y)
               ,&quot;B&quot;:a_index}
      }
    }
    SysGet,B,MonitorWorkArea,% last
    return {&quot;X&quot;:(X+W&gt;BRight?BRight-W:X&lt;BLeft?BLeft:X)
           ,&quot;Y&quot;:(Y+H&gt;BBottom?BBottom-H:Y&lt;BTop?BTop:Y)
           ,&quot;B&quot;:a_index}
  }
}
</code></pre></div>]]></summary>
			<author>
				<name><![CDATA[Alectric]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=26027</uri>
			</author>
			<updated>2024-07-20T16:55:43Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?id=18258&amp;action=new</id>
		</entry>
		<entry>
			<title type="html"><![CDATA[AHK: Обмен данными между скриптами с помощью сервера XAMPP]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?id=18174&amp;action=new" />
			<summary type="html"><![CDATA[<p>Понадобилось для одного проекта решить вопрос с обменом данными между 10 компьютерами глобальной сети.<br />Через соккеты сервер-&gt;клиент делать это было заморочно, так как каждый компьютер должен был являться как сервером, так и клиентом, к тому же айпи у них время от времени менются.<br />Поэтому решил установить на винду веб-сервер - XAMPP.<br /><a href="https://www.apachefriends.org/index.html">https://www.apachefriends.org/index.html</a><br />При установке можно убрать все галочки - достаточно Apache и PHP.<br />Изначально он устанавливается без доступа с глобальной сети.<br />Для разрешения подключения к нему с интернета, нужно заменить в файле httpd-xampp.conf в блоке Alias /phpmyadmin<br /></p><div class="codebox"><pre><code>Require local</code></pre></div><p>на<br /></p><div class="codebox"><pre><code>Require all granted</code></pre></div><p>После чего подсоединяться к нему через своё айпи (возможно понадобится перенаправить порты).<br />Если же айпи меняется, то можно использовать какой-нибудь динамический DNS сервис, например:<br /><a href="https://www.dynu.com/DynamicDNS/IPUpdateClient/WindowsClient">https://www.dynu.com/DynamicDNS/IPUpdat … dowsClient</a><br />После установки клиента, он будет следить за всеми изменениями через заданный промежуток времени.<br />В директории xampp\htdocs создаем новую папку, например, test.<br />И туда пишем простейшую серверную часть index.php, типа:<br /></p><div class="codebox"><pre><code>&lt;?php
$databaseFile = &#039;database.txt&#039;;
$data = file_get_contents($databaseFile);
$method = $_SERVER[&#039;REQUEST_METHOD&#039;];

switch ($method) {
    case ($method == &#039;GET&#039;):
        header(&#039;Content-Type: text/html; charset=utf-8&#039;);
        echo $data;
        break;
    case ($method == &#039;POST&#039;):
        header(&#039;Content-Type: text/html; charset=utf-8&#039;);
        $requestBody = file_get_contents(&#039;php://input&#039;);
		$data .= $requestBody;
        file_put_contents($databaseFile, $data);
        echo &quot;post&quot;;
        break;
    default:
        http_response_code(404);
        echo &quot;404 error&quot;;
        break;
}</code></pre></div><p>Создаем там же файл database.txt.<br />При методе GET сервер возвращает текст из database.txt.<br />При методе POST сервер добавляет данные из запроса в database.txt.<br />При необходимости можно использовать json - в php с этим без проблем.<br />Интересный момент, что при посылании POST запроса на сервер с ссылкой типа: &quot;htpp://123.45.67.89:8081/test&quot;,&nbsp; где 8081 - это порт, который я перенаправил в роутере, то сервер получает не POST, а GET.<br />Проблема решается добавлением слеша в конец ссылки: &quot;htpp://123.45.67.89:8081/test/&quot;.<br />Меня устраивало получение данных от сервера через polling, но при необходимости можно прикрутить и websockets api.<br />Пример GET запроса:<br /></p><div class="codebox"><pre><code>HTTP := ComObjCreate(&quot;WinHTTP.WinHTTPRequest.5.1&quot;)
HTTP.Open(&quot;GET&quot;, &quot;htpp://123.45.67.89:8081/test/&quot;, true)
HTTP.SetRequestHeader(&quot;User-Agent&quot;, &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36&quot;)
HTTP.SetRequestHeader(&quot;Pragma&quot;, &quot;no-cache&quot;)
HTTP.SetRequestHeader(&quot;Cache-Control&quot;, &quot;no-cache, no-store&quot;)
HTTP.SetRequestHeader(&quot;If-Modified-Since&quot;, &quot;Sat, 1 Jan 2000 00:00:00 GMT&quot;)
HTTP.Send()
HTTP.WaitForResponse()
msgbox % HTTP.ResponseText</code></pre></div><p>Пример POST запроса:<br /></p><div class="codebox"><pre><code>HTTP := ComObjCreate(&quot;WinHTTP.WinHTTPRequest.5.1&quot;)
HTTP.Open(&quot;POST&quot;, &quot;htpp://123.45.67.89:8081/test/&quot;, true)
HTTP.SetRequestHeader(&quot;User-Agent&quot;, &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36&quot;)
HTTP.SetRequestHeader(&quot;Pragma&quot;, &quot;no-cache&quot;)
HTTP.SetRequestHeader(&quot;Cache-Control&quot;, &quot;no-cache, no-store&quot;)
HTTP.SetRequestHeader(&quot;If-Modified-Since&quot;, &quot;Sat, 1 Jan 2000 00:00:00 GMT&quot;)
HTTP..SetRequestHeader(&quot;Content-Type&quot;, &quot;application/x-www-form-urlencoded&quot;)
HTTP.Send(&quot;some data&quot;)
HTTP.WaitForResponse()
msgbox % HTTP.ResponseText</code></pre></div><p><a href="http://forum.script-coding.com/viewtopic.php?id=18175">Тема для обсуждения</a></p>]]></summary>
			<author>
				<name><![CDATA[Malcev]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=26930</uri>
			</author>
			<updated>2024-04-25T02:00:55Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?id=18174&amp;action=new</id>
		</entry>
		<entry>
			<title type="html"><![CDATA[AHK: Получение IDCompositionVisual чужого окна]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?id=17983&amp;action=new" />
			<summary type="html"><![CDATA[<p>Недокументированное апи, которое использует виндовс для отображения DWM Thumbnails.<br /><a href="https://blog.adeltax.com/dwm-thumbnails-but-with-idcompositionvisual/">https://blog.adeltax.com/dwm-thumbnails … ionvisual/</a><br />На автохотки как-то так (при нажатии f11 будет примитивная анимация):<br /></p><div class="codebox"><pre><code>targetWindow := WinExist(&quot;ahk_class Shell_TrayWnd&quot;)

if !targetWindow
{
   msgbox targetWindow does not exist
   exitapp
}
DllCall(&quot;LoadLibrary&quot;, &quot;str&quot;, &quot;D3D11.dll&quot;)
DllCall(&quot;LoadLibrary&quot;, &quot;str&quot;, &quot;Dcomp.dll&quot;)
dwmapiLib := DllCall(&quot;LoadLibrary&quot;, &quot;str&quot;, &quot;Dwmapi.dll&quot;, &quot;ptr&quot;)
lDwmpCreateSharedThumbnailVisual := DllCall(&quot;GetProcAddress&quot;, &quot;ptr&quot;, dwmapiLib, &quot;ptr&quot;, 147, &quot;ptr&quot;)
lDwmpQueryWindowThumbnailSourceSize := DllCall(&quot;GetProcAddress&quot;, &quot;ptr&quot;, dwmapiLib, &quot;ptr&quot;, 162, &quot;ptr&quot;)
if !lDwmpCreateSharedThumbnailVisual or !lDwmpQueryWindowThumbnailSourceSize
{
   msgbox dwmapiLib error
   exitapp
}
hr := DllCall(&quot;D3D11\D3D11CreateDevice&quot;, &quot;ptr&quot;, 0, &quot;int&quot;, D3D_DRIVER_TYPE_HARDWARE := 1, &quot;ptr&quot;, 0, &quot;uint&quot;, D3D11_CREATE_DEVICE_BGRA_SUPPORT := 0x20, &quot;ptr&quot;, 0, &quot;uint&quot;, 0, &quot;uint&quot;, D3D11_SDK_VERSION := 7, &quot;ptr*&quot;, direct3dDevice, &quot;ptr*&quot;, 0, &quot;ptr*&quot;, 0)
if (hr != 0)
{
   msgbox D3D11CreateDevice error - %A_LastError%
   exitapp
}
dxgiDevice := ComObjQuery(direct3dDevice, IID_IDXGIDevice := &quot;{54ec77fa-1377-44e6-8c32-88fd5f44c84c}&quot;)
if !dxgiDevice
{
   msgbox dxgiDevice error - %A_LastError%
   exitapp
}
VarSetCapacity(IID_IDCompositionDevice, 16, 0)
DllCall(&quot;ole32\CLSIDFromString&quot;, &quot;str&quot;, &quot;{C37EA93A-E7AA-450D-B16F-9746CB0407F3}&quot;, &quot;ptr&quot;, &amp;IID_IDCompositionDevice)
hr := DllCall(&quot;Dcomp\DCompositionCreateDevice3&quot;, &quot;ptr&quot;, dxgiDevice, &quot;ptr&quot;, &amp;IID_IDCompositionDevice, &quot;ptr*&quot;, dcompDevice)
if (hr != 0)
{
   msgbox DCompositionCreateDevice3 error - %A_LastError%
   exitapp
}
ObjRelease(direct3dDevice)
ObjRelease(dxgiDevice)

VarSetCapacity(windowSize, 8, 0)
hr := DllCall(lDwmpQueryWindowThumbnailSourceSize, &quot;uptr&quot;, targetWindow, &quot;int&quot;, 0, &quot;ptr&quot;, &amp;windowSize)
if (hr != 0)
{
   msgbox lDwmpQueryWindowThumbnailSourceSize error - %A_LastError%
   exitapp
}
width := NumGet(windowSize, 0, &quot;uint&quot;)
height := NumGet(windowSize, 4, &quot;uint&quot;)
VarSetCapacity(DWM_THUMBNAIL_PROPERTIES, 48, 0)
NumPut(0x00000010|0x00000008|0x00000001|0x00000002|0x00000004|0x4000000, DWM_THUMBNAIL_PROPERTIES, 0, &quot;uint&quot;)   ; DWM_TNP_SOURCECLIENTAREAONLY | DWM_TNP_VISIBLE | DWM_TNP_RECTDESTINATION | DWM_TNP_RECTSOURCE | DWM_TNP_OPACITY | DWM_TNP_ENABLE3D
NumPut(width, DWM_THUMBNAIL_PROPERTIES, 12, &quot;uint&quot;)   ; rcDestination
NumPut(height, DWM_THUMBNAIL_PROPERTIES, 16, &quot;uint&quot;)
NumPut(width, DWM_THUMBNAIL_PROPERTIES, 28, &quot;uint&quot;)   ; rcSource
NumPut(height, DWM_THUMBNAIL_PROPERTIES, 32, &quot;uint&quot;) 
NumPut(255, DWM_THUMBNAIL_PROPERTIES, 36, &quot;uchar&quot;)   ; opacity
NumPut(1, DWM_THUMBNAIL_PROPERTIES, 37, &quot;uint&quot;)   ; fVisible

Gui +hwndmyWnd -DPIScale
Gui Show, w%width% h%height%

hr := DllCall(lDwmpCreateSharedThumbnailVisual, &quot;uptr&quot;, myWnd, &quot;uptr&quot;, targetWindow, &quot;uint&quot;, 2, &quot;ptr&quot;, &amp;DWM_THUMBNAIL_PROPERTIES, &quot;ptr&quot;, dcompDevice, &quot;ptr*&quot;, windowVisual, &quot;ptr*&quot;, hThumbWindow)
if (hr != 0)
{
   msgbox lDwmpCreateSharedThumbnailVisual error - %A_LastError%
   exitapp
}
hr := DllCall(NumGet(NumGet(dcompDevice+0)+6*A_PtrSize), &quot;ptr&quot;, dcompDevice, &quot;uptr&quot;, myWnd, &quot;int&quot;, 0, &quot;ptr*&quot;, dcompTarget)   ; IDCompositionDevice::CreateTargetForHwnd
if (hr != 0)
{
   msgbox IDCompositionDevice::CreateTargetForHwnd error - %A_LastError%
   exitapp
}
DllCall(NumGet(NumGet(dcompDevice+0)+19*A_PtrSize), &quot;ptr&quot;, dcompDevice, &quot;ptr*&quot;, Scale3D)   ; IDCompositionDevice::CreateScaleTransform3D
DllCall(NumGet(NumGet(dcompDevice+0)+23*A_PtrSize), &quot;ptr&quot;, dcompDevice, &quot;ptr*&quot;, EffectGroup)   ; IDCompositionDevice::CreateEffectGroup
DllCall(NumGet(NumGet(dcompDevice+0)+25*A_PtrSize), &quot;ptr&quot;, dcompDevice, &quot;ptr*&quot;, Animation)   ; IDCompositionDevice2::CreateAnimation
DllCall(NumGet(NumGet(dcompTarget+0)+3*A_PtrSize), &quot;ptr&quot;, dcompTarget, &quot;ptr&quot;, windowVisual)   ; IDCompositionTarget::SetRoot
DllCall(NumGet(NumGet(dcompDevice+0)+3*A_PtrSize), &quot;ptr&quot;, dcompDevice)   ; IDCompositionDevice::Commit
return

f11::
DllCall(NumGet(NumGet(Animation+0)+5*A_PtrSize), &quot;ptr&quot;, Animation, &quot;double&quot;, 0, &quot;float&quot;, 1, &quot;float&quot;, -1, &quot;float&quot;, 0, &quot;float&quot;, 0)   ; IDCompositionAnimation::AddCubic
DllCall(NumGet(NumGet(Animation+0)+5*A_PtrSize), &quot;ptr&quot;, Animation, &quot;double&quot;, 0.5, &quot;float&quot;, 0.5, &quot;float&quot;, 1, &quot;float&quot;, 0, &quot;float&quot;, 0)   ; IDCompositionAnimation::AddCubic
DllCall(NumGet(NumGet(Animation+0)+8*A_PtrSize), &quot;ptr&quot;, Animation, &quot;double&quot;, 1, &quot;float&quot;, 1)   ; IDCompositionAnimation::End

DllCall(NumGet(NumGet(Scale3D+0)+3*A_PtrSize), &quot;ptr&quot;, Scale3D, &quot;ptr&quot;, Animation)   ; IDCompositionScaleTransform3D::SetScaleX(IDCompositionAnimation*)
DllCall(NumGet(NumGet(Scale3D+0)+5*A_PtrSize), &quot;ptr&quot;, Scale3D, &quot;ptr&quot;, Animation)   ; IDCompositionScaleTransform3D::SetScaleY(IDCompositionAnimation*)
DllCall(NumGet(NumGet(Scale3D+0)+7*A_PtrSize), &quot;ptr&quot;, Scale3D, &quot;ptr&quot;, Animation)   ; IDCompositionScaleTransform3D::SetScaleZ(IDCompositionAnimation*)
DllCall(NumGet(NumGet(Scale3D+0)+10*A_PtrSize), &quot;ptr&quot;, Scale3D, &quot;float&quot;, width/2)   ; IDCompositionScaleTransform3D::SetCenterX(float)
DllCall(NumGet(NumGet(Scale3D+0)+12*A_PtrSize), &quot;ptr&quot;, Scale3D, &quot;float&quot;, height/2)   ; IDCompositionScaleTransform3D::SetCenterY(float)

DllCall(NumGet(NumGet(EffectGroup+0)+5*A_PtrSize), &quot;ptr&quot;, EffectGroup, &quot;ptr&quot;, Scale3D)   ; IDCompositionEffectGroup::SetTransform3D
DllCall(NumGet(NumGet(windowVisual+0)+10*A_PtrSize), &quot;ptr&quot;, windowVisual, &quot;ptr&quot;, EffectGroup)   ; IDCompositionVisual::SetEffect
DllCall(NumGet(NumGet(dcompTarget+0)+3*A_PtrSize), &quot;ptr&quot;, dcompTarget, &quot;ptr&quot;, windowVisual)   ; IDCompositionTarget::SetRoot
DllCall(NumGet(NumGet(dcompDevice+0)+3*A_PtrSize), &quot;ptr&quot;, dcompDevice)   ; IDCompositionDevice::Commit
return</code></pre></div><p>Пример вертикального отображения нескольких visual (окна должны вертикально друг за другом помещаться на рабочем столе), при нажатии f1, f2, f3...&nbsp; анимация:<br /></p><div class="codebox"><pre><code>targetWindows := [WinExist(&quot;ahk_class Shell_TrayWnd&quot;), 0x1307fe, 0x3904ce]

oHotkeys := {}
loop % targetWindows.length()
{
   if !WinExist(&quot;ahk_id &quot; targetWindows[A_Index])
   {
      msgbox Window %A_Index% does not exist
      exitapp
   }
   hotkey := &quot;f&quot; A_Index
   oHotkeys[hotkey] := A_Index
   Hotkey, % hotkey, animation
}
DllCall(&quot;LoadLibrary&quot;, &quot;str&quot;, &quot;D3D11.dll&quot;)
DllCall(&quot;LoadLibrary&quot;, &quot;str&quot;, &quot;Dcomp.dll&quot;)
dwmapiLib := DllCall(&quot;LoadLibrary&quot;, &quot;str&quot;, &quot;Dwmapi.dll&quot;, &quot;ptr&quot;)
lDwmpCreateSharedThumbnailVisual := DllCall(&quot;GetProcAddress&quot;, &quot;ptr&quot;, dwmapiLib, &quot;ptr&quot;, 147, &quot;ptr&quot;)
lDwmpQueryWindowThumbnailSourceSize := DllCall(&quot;GetProcAddress&quot;, &quot;ptr&quot;, dwmapiLib, &quot;ptr&quot;, 162, &quot;ptr&quot;)
if !lDwmpCreateSharedThumbnailVisual or !lDwmpQueryWindowThumbnailSourceSize
{
   msgbox dwmapiLib error
   exitapp
}
hr := DllCall(&quot;D3D11\D3D11CreateDevice&quot;, &quot;ptr&quot;, 0, &quot;int&quot;, D3D_DRIVER_TYPE_HARDWARE := 1, &quot;ptr&quot;, 0, &quot;uint&quot;, D3D11_CREATE_DEVICE_BGRA_SUPPORT := 0x20, &quot;ptr&quot;, 0, &quot;uint&quot;, 0, &quot;uint&quot;, D3D11_SDK_VERSION := 7, &quot;ptr*&quot;, direct3dDevice, &quot;ptr*&quot;, 0, &quot;ptr*&quot;, 0)
if (hr != 0)
{
   msgbox D3D11CreateDevice error - %A_LastError%
   exitapp
}
dxgiDevice := ComObjQuery(direct3dDevice, IID_IDXGIDevice := &quot;{54ec77fa-1377-44e6-8c32-88fd5f44c84c}&quot;)
if !dxgiDevice
{
   msgbox dxgiDevice error - %A_LastError%
   exitapp
}
VarSetCapacity(IID_IDCompositionDevice, 16, 0)
DllCall(&quot;ole32\CLSIDFromString&quot;, &quot;str&quot;, &quot;{C37EA93A-E7AA-450D-B16F-9746CB0407F3}&quot;, &quot;ptr&quot;, &amp;IID_IDCompositionDevice)
hr := DllCall(&quot;Dcomp\DCompositionCreateDevice3&quot;, &quot;ptr&quot;, dxgiDevice, &quot;ptr&quot;, &amp;IID_IDCompositionDevice, &quot;ptr*&quot;, dcompDevice)
if (hr != 0)
{
   msgbox DCompositionCreateDevice3 error - %A_LastError%
   exitapp
}
ObjRelease(direct3dDevice)
ObjRelease(dxgiDevice)

widthMax := heightAll := 0
loop % targetWindows.length()
{
   VarSetCapacity(windowSize%A_Index%, 8, 0)
   hr := DllCall(lDwmpQueryWindowThumbnailSourceSize, &quot;uptr&quot;, targetWindows[A_Index], &quot;int&quot;, 0, &quot;ptr&quot;, &amp;windowSize%A_Index%)
   if (hr != 0)
   {
      msgbox lDwmpQueryWindowThumbnailSourceSize error - %A_LastError%
      exitapp
   }
   width%A_Index% := NumGet(windowSize%A_Index%, 0, &quot;uint&quot;)
   height%A_Index% := NumGet(windowSize%A_Index%, 4, &quot;uint&quot;)
   VarSetCapacity(DWM_THUMBNAIL_PROPERTIES%A_Index%, 48, 0)
   NumPut(0x00000010|0x00000008|0x00000001|0x00000002|0x00000004|0x4000000, DWM_THUMBNAIL_PROPERTIES%A_Index%, 0, &quot;uint&quot;)   ; DWM_TNP_SOURCECLIENTAREAONLY | DWM_TNP_VISIBLE | DWM_TNP_RECTDESTINATION | DWM_TNP_RECTSOURCE | DWM_TNP_OPACITY | DWM_TNP_ENABLE3D
   NumPut(width%A_Index%, DWM_THUMBNAIL_PROPERTIES%A_Index%, 12, &quot;uint&quot;)
   NumPut(height%A_Index%, DWM_THUMBNAIL_PROPERTIES%A_Index%, 16, &quot;uint&quot;)
   NumPut(width%A_Index%, DWM_THUMBNAIL_PROPERTIES%A_Index%, 28, &quot;uint&quot;)   ; rcSource
   NumPut(height%A_Index%, DWM_THUMBNAIL_PROPERTIES%A_Index%, 32, &quot;uint&quot;) 
   NumPut(255, DWM_THUMBNAIL_PROPERTIES%A_Index%, 36, &quot;uchar&quot;)   ; opacity
   NumPut(1, DWM_THUMBNAIL_PROPERTIES%A_Index%, 37, &quot;uint&quot;)   ; fVisible
   heightAll += height%A_Index%
   if (width%A_Index% &gt; widthMax)
      widthMax := width%A_Index%
}
if (heightAll &gt; A_ScreenHeight)
{
   msgbox whole height is too long
   exitapp
}

Gui +hwndmyWnd -DPIScale
Gui Show, w%widthMax% h%heightAll%

loop % targetWindows.length()
{
   hr := DllCall(lDwmpCreateSharedThumbnailVisual, &quot;uptr&quot;, myWnd, &quot;uptr&quot;, targetWindows[A_Index], &quot;uint&quot;, 2, &quot;ptr&quot;, &amp;DWM_THUMBNAIL_PROPERTIES%A_Index%, &quot;ptr&quot;, dcompDevice, &quot;ptr*&quot;, windowVisual%A_Index%, &quot;ptr*&quot;, hThumbWindow%A_Index%)
   if (hr != 0)
   {
      msgbox lDwmpCreateSharedThumbnailVisual error - %A_LastError%
      exitapp
   }
}
hr := DllCall(NumGet(NumGet(dcompDevice+0)+6*A_PtrSize), &quot;ptr&quot;, dcompDevice, &quot;uptr&quot;, myWnd, &quot;int&quot;, 0, &quot;ptr*&quot;, dcompTarget)   ; IDCompositionDevice::CreateTargetForHwnd
if (hr != 0)
{
   msgbox IDCompositionDevice::CreateTargetForHwnd error - %A_LastError%
   exitapp
}
DllCall(NumGet(NumGet(dcompDevice+0)+7*A_PtrSize), &quot;ptr&quot;, dcompDevice, &quot;ptr*&quot;, mainVisual)   ; IDCompositionDevice::CreateVisual
DllCall(NumGet(NumGet(dcompTarget+0)+3*A_PtrSize), &quot;ptr&quot;, dcompTarget, &quot;ptr&quot;, mainVisual)   ; IDCompositionTarget::SetRoot
heightPrev := 0
loop % targetWindows.length()
{
   DllCall(NumGet(NumGet(mainVisual+0)+16*A_PtrSize), &quot;ptr&quot;, mainVisual, &quot;ptr&quot;, windowVisual%A_Index%, &quot;int&quot;, 0, &quot;ptr&quot;, 0)   ; IDCompositionVisual::AddVisual
   DllCall(NumGet(NumGet(windowVisual%A_Index%+0)+6*A_PtrSize), &quot;ptr&quot;, windowVisual%A_Index%, &quot;float&quot;, heightPrev)   ; IDCompositionVisual::SetOffsetY(float)
   heightPrev += height%A_Index%
}
DllCall(NumGet(NumGet(dcompDevice+0)+3*A_PtrSize), &quot;ptr&quot;, dcompDevice)   ; IDCompositionDevice::Commit
loop % targetWindows.length()
{
   DllCall(NumGet(NumGet(dcompDevice+0)+19*A_PtrSize), &quot;ptr&quot;, dcompDevice, &quot;ptr*&quot;, Scale3D%A_Index%)   ; IDCompositionDevice::CreateScaleTransform3D
   DllCall(NumGet(NumGet(dcompDevice+0)+23*A_PtrSize), &quot;ptr&quot;, dcompDevice, &quot;ptr*&quot;, EffectGroup%A_Index%)   ; IDCompositionDevice::CreateEffectGroup
}
DllCall(NumGet(NumGet(dcompDevice+0)+25*A_PtrSize), &quot;ptr&quot;, dcompDevice, &quot;ptr*&quot;, Animation)   ; IDCompositionDevice2::CreateAnimation
DllCall(NumGet(NumGet(Animation+0)+5*A_PtrSize), &quot;ptr&quot;, Animation, &quot;double&quot;, 0, &quot;float&quot;, 1, &quot;float&quot;, -1, &quot;float&quot;, 0, &quot;float&quot;, 0)   ; IDCompositionAnimation::AddCubic
DllCall(NumGet(NumGet(Animation+0)+5*A_PtrSize), &quot;ptr&quot;, Animation, &quot;double&quot;, 0.5, &quot;float&quot;, 0.5, &quot;float&quot;, 1, &quot;float&quot;, 0, &quot;float&quot;, 0)   ; IDCompositionAnimation::AddCubic
DllCall(NumGet(NumGet(Animation+0)+8*A_PtrSize), &quot;ptr&quot;, Animation, &quot;double&quot;, 1, &quot;float&quot;, 1)   ; IDCompositionAnimation::End
return

animation:
n := oHotkeys[A_ThisHotkey]
DllCall(NumGet(NumGet(Scale3D%n%+0)+3*A_PtrSize), &quot;ptr&quot;, Scale3D%n%, &quot;ptr&quot;, Animation)   ; IDCompositionScaleTransform3D::SetScaleX(IDCompositionAnimation*)
DllCall(NumGet(NumGet(Scale3D%n%+0)+5*A_PtrSize), &quot;ptr&quot;, Scale3D%n%, &quot;ptr&quot;, Animation)   ; IDCompositionScaleTransform3D::SetScaleY(IDCompositionAnimation*)
DllCall(NumGet(NumGet(Scale3D%n%+0)+7*A_PtrSize), &quot;ptr&quot;, Scale3D%n%, &quot;ptr&quot;, Animation)   ; IDCompositionScaleTransform3D::SetScaleZ(IDCompositionAnimation*)
DllCall(NumGet(NumGet(Scale3D%n%+0)+10*A_PtrSize), &quot;ptr&quot;, Scale3D%n%, &quot;float&quot;, width%n%/2)   ; IDCompositionScaleTransform3D::SetCenterX(float)
DllCall(NumGet(NumGet(Scale3D%n%+0)+12*A_PtrSize), &quot;ptr&quot;, Scale3D%n%, &quot;float&quot;, height%n%/2)   ; IDCompositionScaleTransform3D::SetCenterY(float)
DllCall(NumGet(NumGet(EffectGroup%n%+0)+5*A_PtrSize), &quot;ptr&quot;, EffectGroup%n%, &quot;ptr&quot;, Scale3D%n%)   ; IDCompositionEffectGroup::SetTransform3D
DllCall(NumGet(NumGet(windowVisual%n%+0)+10*A_PtrSize), &quot;ptr&quot;, windowVisual%n%, &quot;ptr&quot;, EffectGroup%n%)   ; IDCompositionVisual::SetEffect
DllCall(NumGet(NumGet(dcompDevice+0)+3*A_PtrSize), &quot;ptr&quot;, dcompDevice)   ; IDCompositionDevice::Commit
return</code></pre></div><p>Вариант для отображения всех окон на рабочем столе.<br />При нажатии f11 - анимация, f12 - visual обновляются.<br /></p><div class="codebox"><pre><code>width := 1000   ; gui width
height := 640   ; gui height
hwndsExclude := [0x39064a, 0x1601ca]   ; excluded windows

Gui +hwndmyWnd -DPIScale
Gui Show, w%width% h%height%
VarSetCapacity(enable, 4, 0)
NumPut(1, enable, 0, &quot;int&quot;)
VarSetCapacity(WINCOMPATTRDATA, A_PtrSize*3, 0)
NumPut(WCA_EXCLUDED_FROM_LIVEPREVIEW := 0xD, WINCOMPATTRDATA, 0, &quot;int&quot;)
NumPut(&amp;enable, WINCOMPATTRDATA, A_PtrSize, &quot;ptr&quot;)
NumPut(4, WINCOMPATTRDATA, A_PtrSize*2, &quot;uint&quot;)
DllCall(&quot;SetWindowCompositionAttribute&quot;, &quot;uptr&quot;, myWnd, &quot;ptr&quot;, &amp;WINCOMPATTRDATA)

DllCall(&quot;LoadLibrary&quot;, &quot;str&quot;, &quot;D3D11.dll&quot;)
DllCall(&quot;LoadLibrary&quot;, &quot;str&quot;, &quot;Dcomp.dll&quot;)
dwmapiLib := DllCall(&quot;LoadLibrary&quot;, &quot;str&quot;, &quot;Dwmapi.dll&quot;, &quot;ptr&quot;)
lDwmpCreateSharedMultiWindowVisual := DllCall(&quot;GetProcAddress&quot;, &quot;ptr&quot;, dwmapiLib, &quot;ptr&quot;, 163, &quot;ptr&quot;)
lDwmpUpdateSharedMultiWindowVisual := DllCall(&quot;GetProcAddress&quot;, &quot;ptr&quot;, dwmapiLib, &quot;ptr&quot;, 164, &quot;ptr&quot;)
if !lDwmpCreateSharedMultiWindowVisual or !lDwmpUpdateSharedMultiWindowVisual
{
   msgbox dwmapiLib error
   exitapp
}
hr := DllCall(&quot;D3D11\D3D11CreateDevice&quot;, &quot;ptr&quot;, 0, &quot;int&quot;, D3D_DRIVER_TYPE_HARDWARE := 1, &quot;ptr&quot;, 0, &quot;uint&quot;, D3D11_CREATE_DEVICE_BGRA_SUPPORT := 0x20, &quot;ptr&quot;, 0, &quot;uint&quot;, 0, &quot;uint&quot;, D3D11_SDK_VERSION := 7, &quot;ptr*&quot;, direct3dDevice, &quot;ptr*&quot;, 0, &quot;ptr*&quot;, 0)
if (hr != 0)
{
   msgbox D3D11CreateDevice error - %A_LastError%
   exitapp
}
dxgiDevice := ComObjQuery(direct3dDevice, IID_IDXGIDevice := &quot;{54ec77fa-1377-44e6-8c32-88fd5f44c84c}&quot;)
if !dxgiDevice
{
   msgbox dxgiDevice error - %A_LastError%
   exitapp
}
VarSetCapacity(IID_IDCompositionDevice, 16, 0)
DllCall(&quot;ole32\CLSIDFromString&quot;, &quot;str&quot;, &quot;{C37EA93A-E7AA-450D-B16F-9746CB0407F3}&quot;, &quot;ptr&quot;, &amp;IID_IDCompositionDevice)
hr := DllCall(&quot;Dcomp\DCompositionCreateDevice3&quot;, &quot;ptr&quot;, dxgiDevice, &quot;ptr&quot;, &amp;IID_IDCompositionDevice, &quot;ptr*&quot;, dcompDevice)
if (hr != 0)
{
   msgbox DCompositionCreateDevice3 error - %A_LastError%
   exitapp
}
ObjRelease(direct3dDevice)
ObjRelease(dxgiDevice)

hr := DllCall(lDwmpCreateSharedMultiWindowVisual, &quot;uptr&quot;, myWnd, &quot;ptr&quot;, dcompDevice, &quot;ptr*&quot;, virtualDesktopVisual, &quot;ptr*&quot;, hThumbVirtualDesktop)
if (hr != 0)
{
   msgbox lDwmpCreateSharedMultiWindowVisual error - %A_LastError%
   exitapp
}
VarSetCapacity(monitorSize, 16, 0)
NumPut(a_screenwidth, monitorSize, 8, &quot;uint&quot;)
NumPut(a_screenheight, monitorSize, 12, &quot;uint&quot;)
VarSetCapacity(targetSize, 8, 0)
NumPut(width, targetSize, 0, &quot;uint&quot;)
NumPut(height, targetSize, 4, &quot;uint&quot;)
length := hwndsExclude.length()
if (length = &quot;&quot;)
   length := 0
VarSetCapacity(excludeArray, A_PtrSize*length, 0)
for k, v in hwndsExclude
   NumPut(v, excludeArray, A_PtrSize*(a_index-1), &quot;ptr&quot;)
OSbuildNumber := StrSplit(A_OSVersion, &quot;.&quot;)[3]
if (OSbuildNumber &lt; 20000)
   hr := DllCall(lDwmpUpdateSharedMultiWindowVisual, &quot;uptr&quot;, hThumbVirtualDesktop, &quot;ptr&quot;, 0, &quot;uint&quot;, 0, &quot;ptr&quot;, &amp;excludeArray, &quot;uint&quot;, length, &quot;ptr&quot;, &amp;monitorSize, &quot;ptr&quot;, &amp;targetSize)
else
   hr := DllCall(lDwmpUpdateSharedMultiWindowVisual, &quot;uptr&quot;, hThumbVirtualDesktop, &quot;ptr&quot;, 0, &quot;uint&quot;, 0, &quot;ptr&quot;, &amp;excludeArray, &quot;uint&quot;, length, &quot;ptr&quot;, &amp;monitorSize, &quot;ptr&quot;, &amp;targetSize, &quot;uint&quot;, 1)
if (hr != 0)
{
   msgbox lDwmpUpdateSharedMultiWindowVisual error - %A_LastError%
   exitapp
}
hr := DllCall(NumGet(NumGet(dcompDevice+0)+6*A_PtrSize), &quot;ptr&quot;, dcompDevice, &quot;uptr&quot;, myWnd, &quot;int&quot;, 0, &quot;ptr*&quot;, dcompTarget)   ; IDCompositionDevice::CreateTargetForHwnd
if (hr != 0)
{
   msgbox IDCompositionDevice::CreateTargetForHwnd error - %A_LastError%
   exitapp
}
DllCall(NumGet(NumGet(dcompDevice+0)+19*A_PtrSize), &quot;ptr&quot;, dcompDevice, &quot;ptr*&quot;, Scale3D)   ; IDCompositionDevice::CreateScaleTransform3D
DllCall(NumGet(NumGet(dcompDevice+0)+23*A_PtrSize), &quot;ptr&quot;, dcompDevice, &quot;ptr*&quot;, EffectGroup)   ; IDCompositionDevice::CreateEffectGroup
DllCall(NumGet(NumGet(dcompDevice+0)+25*A_PtrSize), &quot;ptr&quot;, dcompDevice, &quot;ptr*&quot;, Animation)   ; IDCompositionDevice2::CreateAnimation
DllCall(NumGet(NumGet(dcompTarget+0)+3*A_PtrSize), &quot;ptr&quot;, dcompTarget, &quot;ptr&quot;, virtualDesktopVisual)   ; IDCompositionTarget::SetRoot
DllCall(NumGet(NumGet(dcompDevice+0)+3*A_PtrSize), &quot;ptr&quot;, dcompDevice)   ; IDCompositionDevice::Commit
return

f11::
DllCall(NumGet(NumGet(Animation+0)+5*A_PtrSize), &quot;ptr&quot;, Animation, &quot;double&quot;, 0, &quot;float&quot;, 1, &quot;float&quot;, -1, &quot;float&quot;, 0, &quot;float&quot;, 0)   ; IDCompositionAnimation::AddCubic
DllCall(NumGet(NumGet(Animation+0)+5*A_PtrSize), &quot;ptr&quot;, Animation, &quot;double&quot;, 0.5, &quot;float&quot;, 0.5, &quot;float&quot;, 1, &quot;float&quot;, 0, &quot;float&quot;, 0)   ; IDCompositionAnimation::AddCubic
DllCall(NumGet(NumGet(Animation+0)+8*A_PtrSize), &quot;ptr&quot;, Animation, &quot;double&quot;, 1, &quot;float&quot;, 1)   ; IDCompositionAnimation::End

DllCall(NumGet(NumGet(Scale3D+0)+3*A_PtrSize), &quot;ptr&quot;, Scale3D, &quot;ptr&quot;, Animation)   ; IDCompositionScaleTransform3D::SetScaleX(IDCompositionAnimation*)
DllCall(NumGet(NumGet(Scale3D+0)+5*A_PtrSize), &quot;ptr&quot;, Scale3D, &quot;ptr&quot;, Animation)   ; IDCompositionScaleTransform3D::SetScaleY(IDCompositionAnimation*)
DllCall(NumGet(NumGet(Scale3D+0)+7*A_PtrSize), &quot;ptr&quot;, Scale3D, &quot;ptr&quot;, Animation)   ; IDCompositionScaleTransform3D::SetScaleZ(IDCompositionAnimation*)
DllCall(NumGet(NumGet(Scale3D+0)+10*A_PtrSize), &quot;ptr&quot;, Scale3D, &quot;float&quot;, width/2)   ; IDCompositionScaleTransform3D::SetCenterX(float)
DllCall(NumGet(NumGet(Scale3D+0)+12*A_PtrSize), &quot;ptr&quot;, Scale3D, &quot;float&quot;, height/2)   ; IDCompositionScaleTransform3D::SetCenterY(float)

DllCall(NumGet(NumGet(EffectGroup+0)+5*A_PtrSize), &quot;ptr&quot;, EffectGroup, &quot;ptr&quot;, Scale3D)   ; IDCompositionEffectGroup::SetTransform3D
DllCall(NumGet(NumGet(virtualDesktopVisual+0)+10*A_PtrSize), &quot;ptr&quot;, virtualDesktopVisual, &quot;ptr&quot;, EffectGroup)   ; IDCompositionVisual::SetEffect
DllCall(NumGet(NumGet(dcompTarget+0)+3*A_PtrSize), &quot;ptr&quot;, dcompTarget, &quot;ptr&quot;, virtualDesktopVisual)   ; IDCompositionTarget::SetRoot
DllCall(NumGet(NumGet(dcompDevice+0)+3*A_PtrSize), &quot;ptr&quot;, dcompDevice)   ; IDCompositionDevice::Commit
return

f12::
if (OSbuildNumber &lt; 20000)
   hr := DllCall(lDwmpUpdateSharedMultiWindowVisual, &quot;uptr&quot;, hThumbVirtualDesktop, &quot;ptr&quot;, 0, &quot;uint&quot;, 0, &quot;ptr&quot;, &amp;excludeArray, &quot;uint&quot;, length, &quot;ptr&quot;, &amp;monitorSize, &quot;ptr&quot;, &amp;targetSize)
else
   hr := DllCall(lDwmpUpdateSharedMultiWindowVisual, &quot;uptr&quot;, hThumbVirtualDesktop, &quot;ptr&quot;, 0, &quot;uint&quot;, 0, &quot;ptr&quot;, &amp;excludeArray, &quot;uint&quot;, length, &quot;ptr&quot;, &amp;monitorSize, &quot;ptr&quot;, &amp;targetSize, &quot;uint&quot;, 1)
if (hr != 0)
{
   msgbox lDwmpUpdateSharedMultiWindowVisual error - %A_LastError%
   exitapp
}</code></pre></div><p><a href="http://forum.script-coding.com/viewtopic.php?pid=159431">Тема для обсуждения</a></p>]]></summary>
			<author>
				<name><![CDATA[Malcev]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=26930</uri>
			</author>
			<updated>2023-11-03T22:22:56Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?id=17983&amp;action=new</id>
		</entry>
		<entry>
			<title type="html"><![CDATA[AHK: Закрепить приложение на панели задач с недокументированным апи]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?id=17968&amp;action=new" />
			<summary type="html"><![CDATA[<p><a href="https://geelaw.blog/entries/msedge-pins">https://geelaw.blog/entries/msedge-pins</a><br /><a href="https://github.com/vhanla/W1nDro1d/blob/main/TaskbarPinner.pas">https://github.com/vhanla/W1nDro1d/blob … Pinner.pas</a><br /></p><div class="codebox"><pre><code>msgbox % GetAllPinned()
pin(&quot;C:\Windows\System32\Notepad.exe&quot;)
msgbox pinned
unpin(&quot;C:\Windows\System32\Notepad.exe&quot;)
msgbox unpinned

pin(app)   ; https://geelaw.blog/entries/msedge-pins https://github.com/vhanla/W1nDro1d/blob/main/TaskbarPinner.pas
{
   static oIPinnedList3 := TaskbandPin_Init()
   static FOLDERID_System := SHGetKnownFolderPath(&quot;{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}&quot;)
   if A_Is64bitOS and (A_PtrSize = 4) and InStr(app, FOLDERID_System)
   {
      msgbox For applications in %FOLDERID_System% need to run script with ahk64.
      exitapp
   }
   pidl := DllCall(&quot;Shell32.dll\ILCreateFromPathW&quot;, &quot;wstr&quot;, app, &quot;ptr&quot;)
   if DllCall(NumGet(NumGet(oIPinnedList3+0)+9*A_PtrSize), &quot;ptr&quot;, oIPinnedList3, &quot;ptr&quot;, pidl)  ; IsPinned
      DllCall(NumGet(NumGet(oIPinnedList3+0)+16*A_PtrSize), &quot;ptr&quot;, oIPinnedList3, &quot;ptr&quot;, 0, &quot;ptr&quot;, pidl, &quot;int&quot;, PLMC_EXPLORER := 4)   ; modify
   DllCall(&quot;Shell32.dll\ILFree&quot;, &quot;ptr&quot;, pidl)
}

unpin(app)
{
   static oIPinnedList3 := TaskbandPin_Init()
   static FOLDERID_System := SHGetKnownFolderPath(&quot;{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}&quot;)
   if A_Is64bitOS and (A_PtrSize = 4) and InStr(app, FOLDERID_System)
   {
      msgbox For applications in %FOLDERID_System% need to run script with ahk64.
      exitapp
   }
   pidl := DllCall(&quot;Shell32.dll\ILCreateFromPathW&quot;, &quot;wstr&quot;, app, &quot;ptr&quot;)
   if !DllCall(NumGet(NumGet(oIPinnedList3+0)+9*A_PtrSize), &quot;ptr&quot;, oIPinnedList3, &quot;ptr&quot;, pidl)  ; IsPinned
      DllCall(NumGet(NumGet(oIPinnedList3+0)+16*A_PtrSize), &quot;ptr&quot;, oIPinnedList3, &quot;ptr&quot;, pidl, &quot;ptr&quot;, 0, &quot;int&quot;, PLMC_EXPLORER := 4)   ; modify
   DllCall(&quot;Shell32.dll\ILFree&quot;, &quot;ptr&quot;, pidl)
}

GetAllPinned()
{
   static oIPinnedList3 := TaskbandPin_Init()
   DllCall(NumGet(NumGet(oIPinnedList3+0)+3*A_PtrSize), &quot;ptr&quot;, oIPinnedList3, &quot;ptr*&quot;, IEnumFullIDList)  ; EnumObjects
   DllCall(NumGet(NumGet(IEnumFullIDList+0)+5*A_PtrSize), &quot;ptr&quot;, IEnumFullIDList)   ; reset
   loop
   {
      if DllCall(NumGet(NumGet(IEnumFullIDList+0)+3*A_PtrSize), &quot;ptr&quot;, IEnumFullIDList, &quot;uint&quot;, 1, &quot;ptr*&quot;, pidl, &quot;uint*&quot;, null)   ; next
         break
      VarSetCapacity(FilePath, 512, 0)
      DllCall(&quot;Shell32.dll\SHGetPathFromIDListW&quot;, &quot;ptr&quot;, pidl, &quot;str&quot;, FilePath)
      FilePaths .= FilePath &quot;`n&quot;
      DllCall(&quot;Ole32.dll\CoTaskMemFree&quot;, &quot;ptr&quot;, pidl)
   }
   return FilePaths
}

TaskbandPin_Init()
{
   static oIPinnedList3
   if !oIPinnedList3
      oIPinnedList3 := ComObjCreate(CLSID_TaskbandPin := &quot;{90AA3A4E-1CBA-4233-B8BB-535773D48449}&quot;, IPinnedList3 := &quot;{0DD79AE2-D156-45D4-9EEB-3B549769E940}&quot;)
   return oIPinnedList3
}

SHGetKnownFolderPath(FOLDERID, KF_FLAG:=0) {                  ;   By SKAN on D356 @ tiny.cc/t-75602 
Local CLSID, pPath:=&quot;&quot;                                        ; Thanks teadrinker @ tiny.cc/p286094
Return Format(&quot;{4:}&quot;, VarSetCapacity(CLSID, 16, 0)
     , DllCall(&quot;ole32\CLSIDFromString&quot;, &quot;Str&quot;,FOLDERID, &quot;Ptr&quot;,&amp;CLSID)
     , DllCall(&quot;shell32\SHGetKnownFolderPath&quot;, &quot;Ptr&quot;,&amp;CLSID, &quot;UInt&quot;,KF_FLAG, &quot;Ptr&quot;,0, &quot;PtrP&quot;,pPath)
     , StrGet(pPath, &quot;utf-16&quot;)
     , DllCall(&quot;ole32\CoTaskMemFree&quot;, &quot;Ptr&quot;,pPath))
}</code></pre></div><p><a href="http://forum.script-coding.com/viewtopic.php?pid=159314">Тема для обсуждения</a></p>]]></summary>
			<author>
				<name><![CDATA[Malcev]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=26930</uri>
			</author>
			<updated>2023-10-08T12:04:48Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?id=17968&amp;action=new</id>
		</entry>
		<entry>
			<title type="html"><![CDATA[AHK: Запуск скрипта с UIAccess с токеном от системного процесса.]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?id=17864&amp;action=new" />
			<summary type="html"><![CDATA[<p>Нашел интересный хак: как запустить скрипт с флагом UIAccess без использования AutoHotkeyU64_UIA, а с присваиванием токена из системного процесса.<br /><a href="https://www.autoitscript.com/forum/topic/210526-make-your-program-super-topmost-uiaccess-udf/">https://www.autoitscript.com/forum/topi … ccess-udf/</a><br />На ahk будет как-то так (нужно запускать скрипт от имени администратора):<br /></p><div class="codebox"><pre><code>#SingleInstance Force
RunWithUiAccess()
Gui, +HWNDhGui +AlwaysOnTop
DllCall(&quot;GetWindowBand&quot;, &quot;uptr&quot;, hGui, &quot;uint*&quot;, band)
msgbox % band
return

RunWithUiAccess()
{
   Gui, +HWNDhGui +AlwaysOnTop
   DllCall(&quot;GetWindowBand&quot;, &quot;uptr&quot;, hGui, &quot;uint*&quot;, band)
   Gui, Destroy
   hGui := &quot;&quot;
   if (band = 1)
   {
      if !DllCall(&quot;advapi32\LookupPrivilegeValueW&quot;, &quot;ptr&quot;, 0, &quot;str&quot;, &quot;SeTcbPrivilege&quot;, &quot;int64*&quot;, luid)
      {
         msgbox LookupPrivilegeValueW error`n%A_LastError%
         exitapp
      }
      VarSetCapacity(PRIVILEGE_SET, 20, 0)
      NumPut(1, PRIVILEGE_SET, 0, &quot;uint&quot;)
      NumPut(PRIVILEGE_SET_ALL_NECESSARY := 1, PRIVILEGE_SET, 4, &quot;uint&quot;)
      NumPut(luid, PRIVILEGE_SET, 8, &quot;int64&quot;)
      DllCall(&quot;advapi32\OpenProcessToken&quot;, &quot;ptr&quot;, DllCall(&quot;GetCurrentProcess&quot;, &quot;ptr&quot;), &quot;uint&quot;, 0x0008|0x0002, &quot;ptr*&quot;, hTokenSelf)   ; TOKEN_QUERY|TOKEN_DUPLICATE
      DllCall(&quot;advapi32\GetTokenInformation&quot;, &quot;ptr&quot;, hTokenSelf, &quot;uint&quot;, 12, &quot;uint*&quot;, TokenSelfSessionId, &quot;uint&quot;, 4, &quot;uint*&quot;, ReturnLength)
      hSnapshot := DllCall(&quot;CreateToolhelp32Snapshot&quot;, &quot;uint&quot;, TH32CS_SNAPPROCESS := 0x00000002, &quot;uint&quot;, 0, &quot;ptr&quot;)
      VarSetCapacity(PROCESSENTRY32, A_PtrSize*3 + 544, 0)
      NumPut(A_PtrSize*3 + 544, PROCESSENTRY32, 0, &quot;uint&quot;)
      loop
      {
         if (A_Index = 1)
            hr := DllCall(&quot;Process32FirstW&quot;, &quot;ptr&quot;, hSnapshot, &quot;ptr&quot;, &amp;PROCESSENTRY32)
         else
            hr := DllCall(&quot;Process32NextW&quot;, &quot;ptr&quot;, hSnapshot, &quot;ptr&quot;, &amp;PROCESSENTRY32)
         if !hr
            break
         if (StrGet(&amp;PROCESSENTRY32 + A_PtrSize*2 + 28, &quot;utf-16&quot;) = &quot;winlogon.exe&quot;)
         {
            hProcess := DllCall(&quot;OpenProcess&quot;, &quot;uint&quot;, PROCESS_QUERY_LIMITED_INFORMATION := 0x1000, &quot;int&quot;, false, &quot;uint&quot;, NumGet(PROCESSENTRY32, 8, &quot;uint&quot;), &quot;ptr&quot;)
            DllCall(&quot;advapi32\OpenProcessToken&quot;, &quot;ptr&quot;, hProcess, &quot;uint&quot;, 0x0008|0x0002, &quot;ptr*&quot;, hToken)   ; TOKEN_QUERY|TOKEN_DUPLICATE
            DllCall(&quot;advapi32\PrivilegeCheck&quot;, &quot;ptr&quot;, hToken, &quot;ptr&quot;, &amp;PRIVILEGE_SET, &quot;int*&quot;, pfResult)
            if pfResult
            {
               DllCall(&quot;advapi32\GetTokenInformation&quot;, &quot;ptr&quot;, hToken, &quot;uint&quot;, 12, &quot;uint*&quot;, TokenSessionId, &quot;uint&quot;, 4, &quot;uint*&quot;, ReturnLength)
               if (TokenSessionId = TokenSelfSessionId)
               {
                  DllCall(&quot;advapi32\GetTokenInformation&quot;, &quot;ptr&quot;, hToken, &quot;uint&quot;, 1, &quot;ptr&quot;, 0, &quot;uint&quot;, 0, &quot;uint*&quot;, ReturnLength)
                  VarSetCapacity(TokenUser, ReturnLength, 0)
                  DllCall(&quot;advapi32\GetTokenInformation&quot;, &quot;ptr&quot;, hToken, &quot;uint&quot;, 1, &quot;ptr&quot;, &amp;TokenUser, &quot;uint&quot;, ReturnLength, &quot;uint*&quot;, ReturnLength)
                  if DllCall(&quot;advapi32\IsWellKnownSid&quot;, &quot;ptr&quot;, NumGet(TokenUser), &quot;uint&quot;, WinLocalSystemSid := 22)
                     DllCall(&quot;advapi32\DuplicateTokenEx&quot;, &quot;ptr&quot;, hToken, &quot;uint&quot;, TOKEN_IMPERSONATE := 0x0004, &quot;ptr&quot;, 0, &quot;int&quot;, SecurityImpersonation := 2, &quot;int&quot;, TokenImpersonation := 2, &quot;ptr*&quot;, hTokenSystem)
               }
            }
            DllCall(&quot;CloseHandle&quot;, &quot;ptr&quot;, hProcess)
            DllCall(&quot;CloseHandle&quot;, &quot;ptr&quot;, hToken)
            if hTokenSystem
               break
         }
      }
      DllCall(&quot;CloseHandle&quot;, &quot;ptr&quot;, hSnapshot)
      if !hTokenSystem
      {
         msgbox RunWithUiAccess error
         exitapp
      }
      DllCall(&quot;advapi32\SetThreadToken&quot;, &quot;ptr&quot;, 0, &quot;ptr&quot;, hTokenSystem)
      DllCall(&quot;advapi32\DuplicateTokenEx&quot;, &quot;ptr&quot;, hTokenSelf, &quot;uint&quot;, 0x0008|0x0002|0x0001|0x0080, &quot;ptr&quot;, 0, &quot;int&quot;, SecurityAnonymous := 0, &quot;int&quot;, TokenPrimary := 1, &quot;ptr*&quot;, hTokenUIAccess)   ; TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY|TOKEN_ADJUST_DEFAULT
      DllCall(&quot;advapi32\SetTokenInformation&quot;, &quot;ptr&quot;, hTokenUIAccess, &quot;uint&quot;, TokenUIAccess := 26, &quot;uint*&quot;, true, &quot;uint&quot;, 4)
      DllCall(&quot;advapi32\RevertToSelf&quot;)
      DllCall(&quot;CloseHandle&quot;, &quot;ptr&quot;, hTokenSystem)
      DllCall(&quot;CloseHandle&quot;, &quot;ptr&quot;, hTokenSelf)
      VarSetCapacity(PROCESS_INFORMATION, A_PtrSize*2 + 8, 0)
      VarSetCapacity(STARTUPINFO, A_PtrSize*9 + 32, 0)
      NumPut(A_PtrSize*9 + 32, STARTUPINFO, 0, &quot;uint&quot;)
      DllCall(&quot;GetStartupInfo&quot;, &quot;ptr&quot;, &amp;STARTUPINFO)
      DllCall(&quot;advapi32\CreateProcessAsUserW&quot;, &quot;ptr&quot;, hTokenUIAccess, &quot;ptr&quot;, 0, &quot;wstr&quot;, DllCall(&quot;GetCommandLineW&quot;, &quot;wstr&quot;), &quot;ptr&quot;, 0, &quot;ptr&quot;, 0, &quot;int&quot;, 0, &quot;uint&quot;, 0, &quot;ptr&quot;, 0, &quot;wstr&quot;, A_ScriptDir, &quot;ptr&quot;, &amp;STARTUPINFO, &quot;ptr&quot;, &amp;PROCESS_INFORMATION)
      DllCall(&quot;CloseHandle&quot;, &quot;ptr&quot;, NumGet(PROCESS_INFORMATION, 0, &quot;ptr&quot;))
      DllCall(&quot;CloseHandle&quot;, &quot;ptr&quot;, NumGet(PROCESS_INFORMATION, A_PtrSize, &quot;ptr&quot;))
      sleep 1000
   }
}</code></pre></div><p><a href="http://forum.script-coding.com/viewtopic.php?pid=158702">Тема для обсуждения</a></p>]]></summary>
			<author>
				<name><![CDATA[Malcev]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=26930</uri>
			</author>
			<updated>2023-07-10T11:50:43Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?id=17864&amp;action=new</id>
		</entry>
		<entry>
			<title type="html"><![CDATA[AHK: Class Рисование графиков из массива данных]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?id=17789&amp;action=new" />
			<summary type="html"><![CDATA[<p>Класс позволяет рассматривать собранные данные в графическом виде.<br />Поддерживает следующие типы массивов:<br /></p><ul><li><p>простой массив с данными; [ 1, 2, 3, ...]</p></li><li><p>массив данных с меткой времени в формате AHK (YYYYMMDDHH24MISS); [ [1,20230510200000], [2,20230510200001], ...]</p></li><li><p>массив с меткой времени с миллисекундами;&nbsp; [ [1,20230510200000,123], [2,20230510200001,432], ...]</p></li><li><p>массив массивов выше перечисленных типов.</p></li></ul><p>Возможности:<br /></p><ul><li><p>автомасштаб;</p></li><li><p>режим отображения по точкам/по времени;</p></li><li><p>пауза перемещения;</p></li><li><p>переход на точку/время;</p></li><li><p>перемещение графика левой кнопкой мыши;</p></li><li><p>выделение и увеличение выделенного правой кнопкой мыши;</p></li><li><p>возврат к предыдущим параметрам после увеличения кликом правой кн. мыши;</p></li><li><p>отображение значения при наведении мыши на точку;</p></li><li><p>сглаживание &quot;ступенек&quot; линий;</p></li><li><p>регулировка толщины линии;</p></li><li><p>изменения размера шрифта;</p></li><li><p>маркеры диапазона значений с расчетом времени, среднего значения, среднеквадратичного отклонения;</p></li><li><p>маркеры и настройки работают только для выбранного графика;</p></li><li><p>выбор цвета графика.</p></li></ul><p>Совместимость: Windows XP, 7, 10.</p><p>Важно!<br />Для удаления экземпляра класса обязательно необходимо вызвать метод &quot;Delete&quot; до удаления объекта.</p><p><a href="http://forum.script-coding.com/viewtopic.php?pid=158129#p158129">Тема для обсуждения.</a></p><p>Пример:<br /></p><div class="codebox"><pre><code>SetBatchLines,-1

c:=1,d:=0,e:=1

arr1:=[]
arr2:=[1]
arr3:={}
arr3[1]:={}
arr3[2]:={}
arr3[3]:={}
arr3[4]:={}
arr3[5]:={}
arr3[6]:={}

gosub,ddd
settimer,ddd,100
gosub,add2
gosub,add3

gra1:=new Graph()
gra2:=new Graph()
gra3:=new Graph()

gra1.show(700,500)
gra2.show(800,600)
gra3.show(900,700)

gra1.DataArray:=arr1
gra2.DataArray:=arr2
gra3.DataArray:=arr3
return

f1::
gra1.show()
gra2.show()
return

f2::
gra3.show()
return

ddd:
{
  a+=0.1
  if (b&lt;-1 or b&gt;1)
    t:=!t
  if t
    b+=0.005
  else
    b-=0.005
  if (b&gt;1)
  {
    random,d,-1000000000,1000000000
    random,e,-1000000000,1000000000
    random,c,0.5,1.5
  }
  random,var,50.5,100.5
  shift:=0
;var:=11
  Val:=(sin(a)*var*b+shift)

  arr1.Push([Val,A_Now])
  arr3[3].Push([-Val,A_Now,A_MSec])
  arr3[4].Push([Val,A_Now,A_MSec])
  arr3[5].Push([cos(-a*2)*10*b+10,A_Now,A_MSec])
  arr3[6].Push([-(cos(a*2)*10*b+10),A_Now,A_MSec])
}
return

add2:
  random,g,0,1
  if (g!=prevg)
  {
    arr2.Push(g)
    arr3[1].Push(g)
  }
  prevg:=g

  random,v,0,5000
  settimer,add2,% v
return

add3:
  random,g,0,1
  if (g!=prevg3)
  {
    arr3[2].Push(g)
  }
  prevg3:=g

  random,v,0,5000
  settimer,add3,% v
return

esc::
  gra1:=gra1.Delete()
  gra2:=gra2.Delete()
  gra3:=gra3.Delete()
  exitapp
</code></pre></div><p>Class:<br /></p><div class="codebox"><pre><code>class Graph
{
  static hModule:=DllCall(&quot;LoadLibrary&quot;,&quot;str&quot;,&quot;gdiplus&quot;,&quot;Ptr&quot;)
  static pToken, InstanceCount=0
  static WinText:=&quot;Special text to identify the ownership of the Graph window.&quot;
  ;gdiplus\
  static hGdipCreateFont:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreateFont&quot;,&quot;Ptr&quot;)
  static hGdipCreateFontFamilyFromName:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreateFontFamilyFromName&quot;,&quot;Ptr&quot;)
  static hGdipCreateFromHDC:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreateFromHDC&quot;,&quot;Ptr&quot;)
  static hGdipCreatePen1:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreatePen1&quot;,&quot;Ptr&quot;)
  static hGdipCreateSolidFill:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreateSolidFill&quot;,&quot;Ptr&quot;)
  static hGdipCreateStringFormat:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreateStringFormat&quot;,&quot;Ptr&quot;)
  static hGdipDeleteBrush:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDeleteBrush&quot;,&quot;Ptr&quot;)
  static hGdipDeleteFont:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDeleteFont&quot;,&quot;Ptr&quot;)
  static hGdipDeleteFontFamily:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDeleteFontFamily&quot;,&quot;Ptr&quot;)
  static hGdipDeletePen:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDeletePen&quot;,&quot;Ptr&quot;)
  static hGdipDeleteStringFormat:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDeleteStringFormat&quot;,&quot;Ptr&quot;)
  static hGdipDrawEllipse:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDrawEllipse&quot;,&quot;Ptr&quot;)
  static hGdipDrawLine:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDrawLine&quot;,&quot;Ptr&quot;)
  static hGdipDrawLines:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDrawLines&quot;,&quot;Ptr&quot;)
  static hGdipDrawString:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDrawString&quot;,&quot;Ptr&quot;)
  static hGdipFillRectangle:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipFillRectangle&quot;,&quot;Ptr&quot;)
  static hGdiplusShutdown:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdiplusShutdown&quot;,&quot;Ptr&quot;)
  static hGdiplusStartup:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdiplusStartup&quot;,&quot;Ptr&quot;)
  static hGdipMeasureString:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipMeasureString&quot;,&quot;Ptr&quot;)
  static hGdipSetSmoothingMode:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipSetSmoothingMode&quot;,&quot;Ptr&quot;)
  static hGdipSetStringFormatAlign:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipSetStringFormatAlign&quot;,&quot;Ptr&quot;)
  static hGdipSetTextRenderingHint:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipSetTextRenderingHint&quot;,&quot;Ptr&quot;)
  static hGdipReleaseDC:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipReleaseDC&quot;,&quot;Ptr&quot;)
  static hGdipDeleteGraphics:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDeleteGraphics&quot;,&quot;Ptr&quot;)
  static hGdipCreateBitmapFromHBITMAP:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreateBitmapFromHBITMAP&quot;,&quot;Ptr&quot;)
  static hGdipCreateTexture2:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipCreateTexture2&quot;,&quot;Ptr&quot;)
  static hGdipDisposeImage:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDisposeImage&quot;,&quot;Ptr&quot;)
  static hGdipDrawRectangle:=DllCall(&quot;GetProcAddress&quot;,&quot;Ptr&quot;,Graph.hModule,&quot;AStr&quot;,&quot;GdipDrawRectangle&quot;,&quot;Ptr&quot;)

  static min:=-9223372036854775808
  static max:= 9223372036854775807
  static MaxTime:=30*24*3600000 ; миллисекунд

  ChooseColor(pRGB:=0,hOwner:=0,DlgX:=0,DlgY:=0,Palette*)
  {
    F:=&quot;ChooseColor&quot;
    if isFunc(F)
    {
      tmp1:=%F%(pRGB,hOwner,DlgX,DlgY,Palette)
      return tmp1
    }
  }

  __Delete()
  {
    Graph.InstanceCount--
    if (Graph.InstanceCount=0)
    {
      DllCall(Graph.hGdiplusShutdown,&quot;Ptr&quot;,Graph.pToken)
      Graph.pToken:=&quot;&quot;
    }
;    tooltip,% &quot;Деструктор вызван.`n&quot; Graph.InstanceCount
;    sleep,1000
;    tooltip
  }

  Delete()
  {
    obm:=this.UpdateTimer
    settimer,% obm,Delete
    this.UpdateTimer:=&quot;&quot;
    Number:=this.Number
    menu,GraphMenu%Number%settings,DeleteAll
    menu,GraphMenu%Number%,DeleteAll
    DllCall(Graph.hGdipDeleteGraphics,&quot;UPtr&quot;,this.pGraphics)
    DllCall(&quot;SelectObject&quot;,&quot;Ptr&quot;,this.hDC,&quot;Ptr&quot;,this.obm)
    DllCall(&quot;DeleteObject&quot;,&quot;UPtr&quot;,this.hBitmap)
    DllCall(&quot;ReleaseDC&quot;,&quot;Ptr&quot;,this.hGuiG,&quot;Ptr&quot;,this.hDC)
    Gui,% this.hGuiHelp . &quot;:Destroy&quot;
    Gui,% this.hGuiTT . &quot;:Destroy&quot;
    Gui,% this.hGuiG . &quot;:Destroy&quot;
    Gui,% this.hGuiB . &quot;:Destroy&quot;
  }

  __New(Name=&quot;&quot;,UpdatePeriod=50)
  {
; Сохранить дефолтное окно
    SaveDefaultGui:=A_DefaultGui
; Запустить библиотеку гдиплюс
    if !Graph.pToken
    {
      VarSetCapacity(si,A_PtrSize=8 ? 24 : 16,0), si:=Chr(1)
      DllCall(Graph.hGdiplusStartup,&quot;UPtr*&quot;,pToken,&quot;Ptr&quot;,&amp;si,&quot;Ptr&quot;,0)
      Graph.pToken:=pToken
    }
    Graph.InstanceCount++
    this.Number:=Number:=Graph.InstanceCount

    if !Name
      this.Name:=&quot;График &quot; Graph.InstanceCount
    this.UpdatePeriod:=UpdatePeriod

; Добавление меню
    ; Меню настроек
    obm:=ObjBindMethod(this,&quot;MenuAlwaysOnTop&quot;)
    menu,GraphMenu%Number%settings,add,Всегда поверх окон,% obm

    obm:=ObjBindMethod(this,&quot;MenuAutoScale&quot;)
    menu,GraphMenu%Number%settings,add,Автомасштаб,% obm
    menu,GraphMenu%Number%settings,check,Автомасштаб

    obm:=ObjBindMethod(this,&quot;MenuSmooth&quot;)
    menu,GraphMenu%Number%settings,add,Сглаживать ступеньки,% obm
    menu,GraphMenu%Number%settings,check,Сглаживать ступеньки

    menu,GraphMenu%Number%settings,add

    obm:=ObjBindMethod(this,&quot;MenuScaleXByTime&quot;)
    menu,GraphMenu%Number%settings,add,Масштабировать X по времени,% obm,+Radio
    menu,GraphMenu%Number%settings,check,Масштабировать X по времени
    this.ScaleXByTime:=true

    obm:=ObjBindMethod(this,&quot;MenuScaleXByDots&quot;)
    menu,GraphMenu%Number%settings,add,Масштабировать X по точкам,% obm,+Radio

    menu,GraphMenu%Number%settings,add

    obm:=ObjBindMethod(this,&quot;MenuResetFont&quot;)
    menu,GraphMenu%Number%settings,add,Сбросить размер шрифта,% obm

    obm:=ObjBindMethod(this,&quot;MenuGColor&quot;)
    menu,GraphMenu%Number%settings,add,Выбрать цвет графика,% obm

    ; основное меню
    obm:=ObjBindMethod(this,&quot;MenuClose&quot;)
    menu,GraphMenu%Number%,add,Закрыть,% obm

    obm:=ObjBindMethod(this,&quot;MenuPause&quot;)
    menu,GraphMenu%Number%,add,Пауза,% obm

    obm:=ObjBindMethod(this,&quot;MenuForward&quot;)
    menu,GraphMenu%Number%,add,Вперёд,% obm

    obm:=ObjBindMethod(this,&quot;MenuBackward&quot;)
    menu,GraphMenu%Number%,add,Назад,% obm

    obm:=ObjBindMethod(this,&quot;MenuGoTo&quot;)
    menu,GraphMenu%Number%,add,Перейти,% obm

    obm:=ObjBindMethod(this,&quot;MenuMarkers&quot;)
    menu,GraphMenu%Number%,add,Маркеры,% obm

    menu,GraphMenu%Number%,add,Настр.,:GraphMenu%Number%settings

    obm:=ObjBindMethod(this,&quot;MenuHelp&quot;)
    menu,GraphMenu%Number%,add,Справка,% obm

; Создание основного окна
    Gui,Graph%Number%:+HwndhGuiB -sysmenu +MinSize400x100 +Resize
    this.hGuiB:=hGuiB
    Gui,%hGuiB%:Margin,0,0
; Создание окна отрисовки графики
    Gui,Graphics%Number%:+HwndhGuiG +ownerGraph%Number% -Caption +ToolWindow +E0x08080020 ; WS_EX_LAYERED := 0x80000
    this.hGuiG:=hGuiG
    Gui,%hGuiG%:Show, noactivate x0 y0 w1 h1,% &quot;Graphics &quot; Number
; Создание окна перехода по времени
    Gui,GraphGoToT%Number%:+HwndhGuiTT +ownerGraph%Number% -sysmenu -Resize
    this.hGuiTT:=hGuiTT
; Создание окна перехода к точке
    Gui,GraphGoToX%Number%:+HwndhGuiTX +ownerGraph%Number% -sysmenu -Resize
    this.hGuiTX:=hGuiTX
; Создание окна справки
    Gui,GraphHelp%Number%:+HwndhGuiHelp +ownerGraph%Number% -sysmenu -Resize
    this.hGuiHelp:=hGuiHelp

; Добавление элементов основного окна
    Gui,%hGuiB%:Menu,GraphMenu%Number%
    Gui,%hGuiB%:font,s10

    ; специальный текст
    Gui,%hGuiB%:add,text,w0 h0,% Graph.WinText

    ; Позиция графики
    Gui,Graph%Number%:Add,Edit,+hwndhGPos -HScroll -VScroll Disabled ReadOnly w400 h200
    this.hGPos:=hGPos

; Создание основного окна завершено
;    Gui,%hGuiB%:show,noactivate,% this.Name
;    Gui,%hGuiB%:hide

; Добавление элементов окна перехода по времени
    Gui,%hGuiTT%:add,DateTime,+hwndhTT xm+1 w160,HH:mm:ss        dd.MM.yyyy
    this.hTT:=hTT
    Gui,%hGuiTT%:add,button,+hwndhTTOkB xm+5 y+5 w70,Ok
    obm:=ObjBindMethod(this,&quot;TTOk&quot;)
    GuiControl,+g,% hTTOkB,% obm
    Gui,%hGuiTT%:add,button,+hwndhTTCancel x+10 w70,Отмена
    obm:=ObjBindMethod(this,&quot;TTCancel&quot;)
    GuiControl,+g,% hTTCancel,% obm

; Добавление элементов окна перехода к точке
    Gui,%hGuiTX%:add,Edit,+hwndhTX xm+1 w150,1
    this.hTX:=hTX
    Gui,%hGuiTX%:add,button,+hwndhTXOkB xm+5 y+5 w70,Ok
    obm:=ObjBindMethod(this,&quot;TXOk&quot;)
    GuiControl,+g,% hTXOkB,% obm
    Gui,%hGuiTX%:add,button,+hwndhTXCancel x+10 w70,Отмена
    obm:=ObjBindMethod(this,&quot;TXCancel&quot;)
    GuiControl,+g,% hTXCancel,% obm

; Добавление элементов окна справки
    HelpText=
    (LTrim
      Левая кнопка мыши - перемещение графика.
      Правая кнопка мыши - выделение и увеличение выделенного участка.
      Клик правой кнопкой - возврат к предыдущему масштабу.
    )
    Gui,%hGuiHelp%:add,text,+hwndhHelpText,% HelpText
    Gui,%hGuiHelp%:add,button,+hwndhHelpClose xm+10 y+5 w70,Закрыть
    obm:=ObjBindMethod(this,&quot;HelpClose&quot;)
    GuiControl,+g,% hHelpClose,% obm

; Вернуть права дефолтному окну
    Gui,% SaveDefaultGui . &quot;:Default&quot;

; Создать графику
    this.Create()

; Настройки по умолчанию
    this.Font:=&quot;Lucida Console&quot; ; шрифт
    this.FontSize:=12 ; размер шрифта
    this.SetFont(this.Font,this.FontSize)
    this.Font_Colour:=0xff000000 ; цвет шрифта
    this.Font_Back:=0xd0ffffff ; цвет фона шрифта
    this.WhiteBrush:=0xffffffff ; для общего фона
    this.GrayPen:=0xff808080 ; для оси
    this.GrayPen2:=0xffe0e0e0 ; для оси
    this.RedPen:=0xffff0000 ; для маркера
    this.LightGreenBrush:=0xffd5ffd5 ; для фона информации
    this.LightBlueBrush:=0xffd5d5ff ; для фона Меню
    this.BluePen:=0xff0000ff ; для графика
    this.Pen:=[]
    this.H:=[]

    this.ZoomX:=100
    this.ZoomT:=30000
    this.XTOffset:=0
    this.X_Offset:=0
    this.ZeroPos:=0
    this.ZeroPosU:=1
    this.YScale:=[]
    this.GPosH:=1

    this.AStartTime:=0
    this.StampOffset:=0
    this.MarkerOffset:=0
    this.StampsNum:=5
    this.XMarker:=[]
    this.MenuOffset:=0
    this.Old_XTOffset:=0

    this.Sel:=1
    this.Proc:=1
    this.prevArrSize:=[]
    this.StampT:={}
    this.SearchStop:=[]
    this.SearchSkip:=[]
    this.LineW:=[]
    this.Smooth:=[]
    this.AutoScale:=[]
    this.Savehi_lim:=[]
    this.Savelo_lim:=[]
    this.SaveAutoScale:=[]
    this.prev_ArrSize:=[]
    this.Val_UnderGraph_Drawed:=[]
    this.UnderGX:=[]
    this.UnderGY:=[]
    this.UnderGV:=[]
    this.Lock_hi_lim:=[]
    this.Lock_lo_lim:=[]
    this.GHide:=[]

; Запомнить метку для таймера
    this.UpdateTimer:=ObjBindMethod(this,&quot;UpdateGraph&quot;)

/*
форматы обрабатывемых данных

Data:=[1,2,3,4,5,6,76,56,3,45,6,22,45,234]
 - простой массив значений

Data:=[[1,234],[2,345],[3,456],[4,567]]
 - массив с меткой времени [[значение,время],[значение,время]...]

Data:=[[1,234,56],[2,345,67],[3,456,78],[4,567,89]]
 - массив с меткой времени с миллисекундами [[значение,время,миллисекунды],[значение,время,миллисекунды]...]

Добавить:
Data:=[[[1,234,56],[2,345,67]],[[1,234,56],[2,345,67]],[[1,234,56],[2,345,67]]]
 - массив любых выше перечисленных массивов
*/
  }

  DataArray[]
  {
    set
    {
      this.StampT:={}
      return this.Data:=value
    }
    get
    {
      return this.Data
    }
  }

  HitBox(X,Y,Rect)
  {
    return (X&gt;Rect.X and X&lt;Rect.X+Rect.W and Y&gt;Rect.Y and Y&lt;Rect.Y+Rect.H)
  }

  FloatFormatFix()
  {
; Если в вашем коде где либо использован SetFormat,Float, то расскоментируйте следующие строки
/*
    if this.SWFormat:=!this.SWFormat
    {
      this.SaveFormatFloat:=A_FormatFloat
      SetFormat,Float,0.15
    }
    else
    {
      tmp1:=this.SaveFormatFloat
      SetFormat,Float,%tmp1%
    }
*/
; Тут какая-то чехорда, нужно подумать как сделать лучше...
  }

;/////////////////////////////////////////////////////////////////////////////////////////////////
;|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|
;||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////||
;|||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||
;||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/||||
  UpdateGraph()
  {
    if this.GuiHiden
      return

    this.FloatFormatFix()
    DllCall(&quot;QueryPerformanceFrequency&quot;, &quot;Int64*&quot;, frequency)
    DllCall(&quot;QueryPerformanceCounter&quot;,&quot;Int64*&quot;,EndTime)
    CycleTime:=round((EndTime-this.StartTime)/frequency*1000,1)
    DllCall(&quot;QueryPerformanceCounter&quot;, &quot;Int64*&quot;, StartTime)
    this.StartTime:=StartTime

; Взять координаты мыши
    SaveCoordMode:=A_CoordModeMouse
    CoordMode,Mouse,Client
    MouseGetPos,mX,mY,mW,mC,2
    CoordMode,Mouse,% SaveCoordMode
    MouseIn:=(this.hGPos=mC)
    DeltaX:=this.OldmX-mX
    DeltaY:=this.OldmY-mY

    this.Pos:=this.DrawString(0,0,0,,,,this.Font_Colour,this.Font_Back)
; очистить поле
    this.FillRectangle(this.WhiteBrush,0,0,this.GPosW,this.GPosH_)

; Добавление координаты маркеру
    if (MouseIn and getkeystate(&quot;RButton&quot;,&quot;P&quot;))
      this.XMarker[2]:=mX

; Пауза скролинга для режима отображения по времени
    if this.Pause
    {
      this.XTOffset+=CycleTime
      this.OldXTOffset+=CycleTime
    }

; Определение массива массивов
    ArAr:=isObject(this.Data[1]) and isObject(this.Data[1,1])
       or isObject(this.Data[1]) and !isObject(this.Data[1,1]) and (this.Data[1,2]=&quot;&quot; or strlen(this.Data[1,2])!=14)
    if ArAr
      Ars:=this.Data.MaxIndex()
    else
      Ars:=1

    loop,% Ars
    {
      this.Proc:=a_index
      if (this.prevArrSize[this.Proc]=&quot;&quot;)
      {
        this.prevArrSize[this.Proc]:=1
        this.SearchStop[this.Proc]:=true
        this.SearchSkip[this.Proc]:=0
        this.LineW[this.Proc]:=1
        this.Smooth[this.Proc]:=true
        this.AutoScale[this.Proc]:=true
        this.GHide[this.Proc]:=false
        this.UpdateMenu()
      }

      if (this.YScale[this.Proc]=&quot;&quot;)
        this.YScale[this.Proc]:=1

      if (this.Pen[this.Proc]=&quot;&quot;)
      {
        random,Hue,0,255
        if this.Hue[this.Proc-1]
        {
          loop,100
          {
            random,Hue,0,255
            if (abs(this.Hue[this.Proc-1]-Hue)&gt;45)
              break
          }
        }
        this.Pen[this.Proc]:=Graph.AHSVToARGB(255,Hue,255,192)
        this.Hue[this.Proc]:=Hue
      }

      if (this.SearchSkip[this.Proc] and this.Old_XTOffset&gt;this.XTOffset)
      {
        this.SearchStop[this.Proc]:=true
        this.SearchSkip[this.Proc]:=0
      }
    }
    this.Old_XTOffset:=this.XTOffset


; Обработка массивов данных
    this.ValTipPos:=&quot;&quot;
    loop:
    loop,% Ars
    {
      this.Proc:=a_index
      if ArAr
        this.pData:=this.Data[this.Proc]
      else
        this.pData:=this.Data

      ArrSize:=this.pData.MaxIndex()
      this.ArrSize[this.Proc]:=ArrSize
      this.TimeStampExist:=strlen(this.pData[ArrSize,2])=14
      TimeStampMillis:=this.pData[ArrSize,3]!=&quot;&quot;

      Period:=this.GPosW/this.ZoomX ; количество пикселей на точку по X
      PerioT:=this.GPosW/this.ZoomT ; количество пикселей на миллисекунду по X
      this.PerioT:=PerioT
      Pixel:=this.GPosH/this.YScale[this.Proc] ; сколько пикселей занимает 1 единица по Y
      ZeroPos:=this.ZeroPosU
; Пауза скролинга для режима отображения по точкам
      if (this.Pause and this.prev_ArrSize[this.Proc] and ArrSize!=this.prev_ArrSize[this.Proc])
      {
        tmp1:=ArrSize-this.prev_ArrSize[this.Proc]
        this.X_Offset+=tmp1
        this.OldX_Offset+=tmp1
      }
      this.prev_ArrSize[this.Proc]:=ArrSize

; Перемещение графика
      L_Button:
      if (MouseIn and getkeystate(&quot;LButton&quot;,&quot;P&quot;) and mY&lt;this.GPosH and !this.Setting)
      {
        if !this.GetmCoordOnce
        {
          this.LButtonStart:=a_tickcount
          this.Old_mX:=mX
          this.Old_mY:=mY
          this.OldX_Offset:=this.X_Offset
          this.OldXTOffset:=this.XTOffset
          this.GetmCoordOnce:=true
          this.OffsetSetting:=true
          this.ShiftStrt:=true
        }
        this.X_Offset:=this.OldX_Offset-round((this.Old_mX-mX)/Period)
        this.XTOffset:=this.OldXTOffset-round((this.Old_mX-mX)/PerioT)
        this.ConstrainXOffset()

        DeltaY:=this.Old_mY-mY
        if (this.Sel=this.Proc)
          this.ZeroPos+=DeltaY
        if (this.Sel=this.Proc)
          this.Old_mY:=mY

        if (this.OldX_Offset-this.X_Offset!=0)
          this.MouseMoved:=true
      }
      else if (this.GetmCoordOnce and !getkeystate(&quot;LButton&quot;,&quot;P&quot;))
      {
        this.GetmCoordOnce:=false
        this.OffsetSetting:=false
        ; Добавление координаты маркеру
        if !this.MouseMoved
          this.XMarker[1]:=mX
        this.MouseMoved:=false
      }
      if !getkeystate(&quot;LButton&quot;,&quot;P&quot;)
        this.SearchStop[this.Proc]:=false
; Выделение участка
      R_Button:
      if (MouseIn and getkeystate(&quot;RButton&quot;,&quot;P&quot;) and mY&lt;this.GPosH and !this.Setting)
      {
        if !this.GetStartPosRectOnce
        {
          this.GetStartPosRectOnce:=true
          this.RectXs:=mX
          this.RectYs:=mY
        }
        if (this.RectXs&gt;mX)
          this.RectX:=mX
        else
          this.RectX:=this.RectXs
        if (this.RectYs&gt;mY)
          this.RectY:=mY
        else
          this.RectY:=this.RectYs
        this.RectW:=abs(this.RectXs-mX)
        this.RectH:=abs(this.RectYs-mY)
        if (this.RectW&gt;10 and this.RectH&gt;10)
          this.DrawRectangle(this.Font_Colour,this.RectX,this.RectY,this.RectW,this.RectH)
        else
          this.DrawRectangle(this.GrayPen2,this.RectX,this.RectY,this.RectW,this.RectH)
      }
      else if (this.GetStartPosRectOnce and !getkeystate(&quot;RButton&quot;,&quot;P&quot;))
      {
        this.GetStartPosRectOnce:=false
        if (this.RectW&gt;10 and this.RectH&gt;10)
        {
          this.RectZoom:=true
          this.SaveZoomX:=this.ZoomX
          this.SaveZoomT:=this.ZoomT
          this.SaveX_Offset:=this.X_Offset
          this.SaveXTOffset:=this.XTOffset
          this.SavePause:=this.Pause
          this.Pause:=true
          this.ShiftStrt:=true
          loop,% Ars
          {
            this.SaveAutoScale[a_index]:=this.AutoScale[a_index]
            this.SaveZeroPos:=this.ZeroPos
            this.SaveYScale[a_index]:=this.YScale[a_index]
            if this.AutoScale[a_index]
            {
              this.AutoScale[a_index]:=false
            }
            P:=this.GPosH/this.YScale[a_index] ; сколько пикселей занимает 1 единица по Y
            this.YScale[a_index]:=this.RectH/P
          }
          this.UpdateMenu()
          U:=this.SaveYScale[this.Sel]/this.GPosH ; сколько единиц занимает 1 пиксель по Y
          P:=this.GPosH/this.YScale[this.Sel] ; сколько пикселей занимает 1 единица по Y
          RectY2:=(this.RectY+this.RectH)
          this.ZeroPos-=((this.ZeroPosU-RectY2)*U)*P+(this.GPosH-this.ZeroPosU)
          this.ZeroPos:=round(this.ZeroPos)
          if !this.ScaleXByTime
          {
            this.ZoomX:=round(this.RectW/Period)+1
            this.X_Offset+=round((this.GPosW-(this.RectX+this.RectW))/Period)
          }
          else
          {
            this.ZoomT:=round(this.RectW/PerioT)
            this.XTOffset+=round((this.GPosW-(this.RectX+this.RectW))/PerioT)
          }
        }
        else if this.RectZoom
        {
          this.RectZoom:=false
          this.ZoomX:=this.SaveZoomX
          this.ZoomT:=this.SaveZoomT
          this.X_Offset:=this.SaveX_Offset
          this.XTOffset:=this.SaveXTOffset
          this.Pause:=this.SavePause
          this.ShiftStrt:=true
          this.ZeroPos:=this.SaveZeroPos
          loop,% Ars
          {
            this.YScale[a_index]:=this.SaveYScale[a_index]
            if ((this.SaveAutoScale[a_index] and !this.AutoScale[a_index]) or (!this.SaveAutoScale[a_index] and this.AutoScale[a_index]))
            {
              this.AutoScale[a_index]:=true
              this.UpdateMenu()
            }
          }
        }
      }

; Обработать колесо мыши
      Whell:
      if ((Graph.WheelUp or Graph.WheelDown) and Graph.HotKeymC=this.hGPos)
      {
        this.ShiftStrt:=true
        CoefHi:=mY/this.GPosH
        CoefLo:=(this.GPosH-mY)/this.GPosH
        tmp1:=round(this.ZoomX/20)+1
        tmt1:=round(this.ZoomT/20)+1
        if (tmp2&gt;tmp3)
          tmp3:=tmp2
        if Graph.WheelUp
        {
          this.ZoomX-=tmp1
          this.ZoomT-=tmt1
        }
        if Graph.WheelDown
        {
          this.ZoomX+=tmp1
          this.ZoomT+=tmt1
        }
        if (this.ZoomX&lt;10)
          this.ZoomX:=10
        if (this.ZoomX&gt;100000)
          this.ZoomX:=100000
        if (this.ZoomT&lt;1000)
          this.ZoomT:=1000
        if (this.ZoomT&gt;Graph.MaxTime)
          this.ZoomT:=Graph.MaxTime
        this.Pause:=true
        tmp1:=(this.GPosW-mX)/Period
        tmp2:=(this.GPosW-mX)/(this.GPosW/this.ZoomX)
        this.X_Offset+=round(tmp1-tmp2)
        tmp1:=(this.GPosW-mX)/PerioT
        tmp2:=(this.GPosW-mX)/(this.GPosW/this.ZoomT)
        this.XTOffset+=round(tmp1-tmp2)
        this.ConstrainXOffset()
      }

; Добавление временной метки массиву без неё
      if !this.TimeStampExist
      {
        if !isObject(this.StampT[this.Proc])
        {
          this.StampT[this.Proc]:=[[A_Now,A_MSec]]
          this.prevArrSize[this.Proc]:=1
        }
        if (ArrSize!=this.prevArrSize[this.Proc])
        {
          tmp2:=this.StampT[this.Proc,this.prevArrSize[this.Proc]]
          tmp3:=Graph.TimeSub([A_Now,A_MSec],tmp2)
          tmp1:=ArrSize-this.prevArrSize[this.Proc]
          tmp3:=tmp3//tmp1
          loop,% tmp1
          {
            add:=a_index*tmp3
            this.StampT[this.Proc].Push(Graph.TimeAddMs(tmp2,add))
          }
          this.prevArrSize[this.Proc]:=ArrSize
        }
      }

; отрисовать сетку
      if (this.Sel=this.Proc)
      {
        tmpc:=Graph.AHSVToARGB(200,this.Hue[this.Proc],this.Hue[this.Proc]&gt;255?0:100,this.Hue[this.Proc]&gt;255?(this.Hue[this.Proc]&gt;&gt;8):255)
        LineH:=this.Pos.H*2
        RoundTo:=1
        GoAgain:
        Drawed:=0
        if (ZeroPos&lt;=this.GPosH+1 and ZeroPos&gt;=0)
        {
          loop,% ZeroPos//LineH
          {
            tmp1:=a_index*LineH
            tmp2:=tmp1/Pixel
            Val:=round(tmp2,-(strlen(round(tmp2))-RoundTo))
            if (Val=0)
              continue
            CoordY:=ZeroPos-Val*Pixel
            if (CoordY&lt;0)
              break
            this.DrawString(&quot; &quot; Val,0,CoordY,,,,this.Font_Colour,tmpc)
            this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
          }
          loop,% (this.GPosH-ZeroPos)//LineH
          {
            tmp1:=a_index*LineH
            tmp2:=tmp1/Pixel
            Val:=round(tmp2,-(strlen(round(tmp2))-RoundTo))
            if (Val=0)
              continue
            CoordY:=ZeroPos+Val*Pixel
            if (CoordY&gt;this.GPosH)
              break
            this.DrawString(&quot;-&quot; Val,0,CoordY-this.Pos.H,,,,this.Font_Colour,tmpc)
            this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
          }
        }
        else if (ZeroPos&gt;this.GPosH)
        {
          loop,% this.GPosH//LineH
          {
            tmp1:=ZeroPos-a_index*LineH
            tmp2:=tmp1/Pixel
            Val:=round(tmp2,-(strlen(round(tmp2))-RoundTo))
            CoordY:=ZeroPos-Val*Pixel
            if (prevCoordY!=CoordY and CoordY&lt;=this.GPosH-this.Pos.H and CoordY&gt;=0)
            {
              this.DrawString(&quot; &quot; Val,0,CoordY,,,,this.Font_Colour,tmpc)
              this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
              Drawed++
            }
            prevCoordY:=CoordY
          }
          if (Drawed&lt;1)
          {
            RoundTo++
            if (RoundTo&lt;10)
              goto,GoAgain
            tmp1:=ZeroPos-this.GPosH/2
            tmp2:=tmp1/Pixel
            Val:=round(tmp2)
            CoordY:=ZeroPos-Val*Pixel
            this.DrawString(&quot; &quot; Val,0,CoordY,,,,this.Font_Colour,tmpc)
            this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
          }
        }
        else if (ZeroPos&lt;0)
        {
          loop,% this.GPosH//LineH
          {
            tmp1:=-ZeroPos+a_index*LineH
            tmp2:=tmp1/Pixel
            Val:=round(tmp2,-(strlen(round(tmp2))-RoundTo))
            CoordY:=ZeroPos+Val*Pixel
            if (prevCoordY!=CoordY and CoordY&lt;=this.GPosH+1 and CoordY&gt;=this.Pos.H)
            {
              this.DrawString(&quot;-&quot; Val,0,CoordY-this.Pos.H,,,,this.Font_Colour,tmpc)
              this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
              Drawed++
            }
            prevCoordY:=CoordY
          }
          if (Drawed&lt;1)
          {
            RoundTo++
            if (RoundTo&lt;10)
              goto,GoAgain
            tmp1:=-ZeroPos+this.GPosH/2
            tmp2:=tmp1/Pixel
            Val:=round(tmp2)
            CoordY:=ZeroPos+Val*Pixel
            this.DrawString(&quot;-&quot; Val,0,CoordY-this.Pos.H,,,,this.Font_Colour,tmpc)
            this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
          }
        }
; отрисовать линию и текст нуля
        if (ZeroPos&lt;=this.GPosH+1 and ZeroPos&gt;=0)
        {
          this.DrawLine(this.GrayPen,0,ZeroPos,this.GPosW,ZeroPos)
          tmp0:=ZeroPos+this.Pos.H//2
          tmp1:=(this.GPosH&gt;tmp0) ? ZeroPos-this.Pos.H//2 : this.GPosH-this.Pos.H
          this.DrawString(&quot;0&quot;,0,tmp1,,,,this.Font_Colour,tmpc)
        }
      }
; сетка отрисована

      if !ArrSize
        this.MessagePos:=this.DrawString(&quot;Нет данных&quot;,this.GPosW//2-this.MessagePos.W//2,this.GPosH//2-this.MessagePos.H//2,,,,this.Font_Colour,this.Font_Back)
      if (this.SearchSkip[this.Proc]&gt;0)
        this.SearchSkip[this.Proc]--

; Отрисовка графика ----------------------------------------------------------------
      hi_lim:=Graph.min
      lo_lim:=Graph.max
      Oldi:=0
      NotScaleXByTime:
      this.LockYP[this.Proc]:=0
      this.LockYM[this.Proc]:=0
      if isobject(this.StampPos)
        StampLine:=this.StampPos.W/2
      else
        StampLine:=0
      this.Val_UnderGraph_Drawed[this.Proc]:=false
      if !this.ScaleXByTime
      {
        Old_CoordY:=ZeroPos
        Old_CoordX:=this.GPosW+10
        GPoints:=[[Old_CoordX,Old_CoordY]]
        if !this.GHide[this.Proc]
        loop,% this.GPosW
        {
          iLast:=a_index=this.GPosW
          i:=round(ArrSize-(a_index/Period+1))-this.X_Offset
          if (i&lt;1 or i&gt;ArrSize) and !iLast
            continue
          if iLast
          {
            if (i-1&gt;0)
              i--
            else
              i:=Oldi
          }
          Aindex++
          if (Aindex=1 and i+1&lt;=ArrSize)
          {
            if !this.TimeStampExist
              Value:=this.pData[i]
            else
              Value:=this.pData[i,1]
            tmp1:=Value?Value:0
            Value:=Value!=&quot;&quot;?Value:&quot;Null&quot;
            if (tmp1!=&quot;&quot;)
            {
              hi_lim:=tmp1&gt;hi_lim ? tmp1 : hi_lim
              lo_lim:=tmp1&lt;lo_lim ? tmp1 : lo_lim
            }
            CoordY:=ZeroPos-tmp1*Pixel
            if (CoordY&lt;-1)
              CoordY:=0
            else if (CoordY&gt;this.GPosH+1)
              CoordY:=this.GPosH
            GPoints.Push([this.GPosW,CoordY])
          }
          if !this.TimeStampExist
            Value:=this.pData[i]
          else
            Value:=this.pData[i,1]
          tmp1:=Value?Value:0
          Value:=Value!=&quot;&quot;?Value:&quot;Null&quot;
          if (tmp1!=&quot;&quot;)
          {
            hi_lim:=tmp1&gt;hi_lim ? tmp1 : hi_lim
            lo_lim:=tmp1&lt;lo_lim ? tmp1 : lo_lim
          }
          CoordX:=this.GPosW-a_index
          CoordY:=ZeroPos-tmp1*Pixel
          SkipLine:=false
          if (CoordY&lt;-1)
          {
            CoordY:=0
            SkipLine:=true
            this.LockYP[this.Proc]++
          }
          else if (CoordY&gt;this.GPosH+1)
          {
            CoordY:=this.GPosH
            SkipLine:=true
            this.LockYM[this.Proc]++
          }
          NewStamp:=false
          NewXMarker:=false
          if (Oldi!=i)
          {
            ; линия графика
            if !this.Smooth[this.Proc]
            {
              GPoints.Push([Old_CoordX,CoordY])
              Old_CoordY:=CoordY
            }
            GPoints.Push([CoordX,CoordY])
            ; Отрисовка подробностей при малом увеличении
            if (Oldi-i&gt;1)
            {
              max:=Graph.min
              min:=Graph.max
              loop,% Oldi-i+1
              {
                j:=i+a_index-1
                if (j&lt;1 or j&gt;ArrSize)
                  continue
                if !this.TimeStampExist
                  tmp1:=this.pData[j]
                else
                  tmp1:=this.pData[j,1]
                min:=min&lt;tmp1?min:tmp1
                max:=max&gt;tmp1?max:tmp1
                if (tmp1!=&quot;&quot;)
                {
                  hi_lim:=tmp1&gt;hi_lim ? tmp1 : hi_lim
                  lo_lim:=tmp1&lt;lo_lim ? tmp1 : lo_lim
                }
              }
              min:=ZeroPos-min*Pixel
              max:=ZeroPos-max*Pixel
              if (min&lt;0)
                min:=1
              if (min&gt;this.GPosH)
                min:=this.GPosH
              if (max&lt;0)
                max:=0
              if (max&gt;this.GPosH)
                max:=this.GPosH-1
              GPoints.Push([CoordX,min])
              GPoints.Push([CoordX,max])
              GPoints.Push([CoordX,CoordY])
            }
            ; Дорисовать точки при большом увеличении
            if (Old_CoordX-CoordX&gt;3)
              this.DrawEllipse(this.Pen[this.Proc],CoordX-1,CoordY-1,2,2,this.LineW[this.Proc])

            ; текст значения под курсором
            if (!this.Val_UnderGraph_Drawed[this.Proc] and MouseIn)
            if (mX&gt;=CoordX and mX&lt;=CoordX+20)
            if (mY&gt;CoordY and mY&lt;CoordY+20)
            {
              this.Val_UnderGraph_Drawed[this.Proc]:=true
              this.UnderGX[this.Proc]:=CoordX
              this.UnderGY[this.Proc]:=CoordY
              this.UnderGV[this.Proc]:=Value
            }
            Old_CoordX:=CoordX
            Old_CoordY:=CoordY
            Oldi:=i
            NewStamp:=true
            if (this.Sel=this.Proc)
              NewXMarker:=true
          }
          ; линия и текст временой шкалы
          if (this.Sel=this.Proc)
          {
            if Period&gt;=1
              StampLine++
            else
              StampLine+=1
            if !isobject(this.StampPos)
              tmp1:=0
            else
              tmp1:=this.StampPos.W+2
            if (NewStamp and StampLine&gt;tmp1) or (!Stamp and a_index=this.GPosW)
            {
              StampLine:=0
              Stamp:=true
              this.DrawLine(this.GrayPen2,CoordX,0,CoordX,this.GPosH)
              if !this.TimeStampExist
              {
                FormatTime,tmp10,% this.StampT[this.Proc,i,1],HH:mm:ss
                tmp10.=&quot;`n&quot; this.StampT[this.Proc,i,2] &quot;ms&quot;
              }
              else
              {
                FormatTime,tmp10,% this.pData[i,2],dd.MM.yyyy&#039;`n&#039;HH:mm:ss
                if (TimeStampMillis and this.pData[i,3]!=&quot;&quot;)
                  tmp10.=&quot;.&quot; this.pData[i,3]
              }
              StampPos:=this.DrawString(tmp10,CoordX-this.StampPos.W/2,this.GPosH,,,,this.Font_Colour,this.GrayPen2)
              if !GetSizeOnce
              {
                GetSizeOnce:=true
                this.StampPos:=StampPos
                this.StampOffset:=this.StampPos.H
              }
              if (prevStampPos.w)
              {
                if (this.StampPos.X+this.StampPos.w&gt;prevStampPos.X)
                  this.StampsNum--
                if (this.StampPos.X+this.StampPos.w*2+5&lt;prevStampPos.X)
                  this.StampsNum++
                if this.StampsNum&lt;2
                  this.StampsNum:=2
              }
              prevStampPos:=this.StampPos
            }
            ; маркеры
            tmp1:=this.GPosW-a_index
            loop,2
            {
              if (NewXMarker and !XMarker%a_index% and tmp1&lt;this.XMarker[a_index])
              {
                XMarker%a_index%:=true
                XMarker%a_index%i:=i
                XMarker%a_index%Coord:=tmp1
              }
            }
            NewXMarker:=false
          }
        }
        ; рисовать линию графика
        if !this.GHide[this.Proc]
          this.DrawLines(this.Pen[this.Proc],GPoints,this.LineW[this.Proc])
        ; блокировать перемещение по Y
        this.LockYP[this.Proc]:=(this.LockYP[this.Proc]&gt;=Aindex-3)?1:0
        this.LockYM[this.Proc]:=(this.LockYM[this.Proc]&gt;=Aindex-3)?1:0
      }

      ScaleXByTime:
      if this.ScaleXByTime
      {
        Strt:=Graph.TimeSubMs([A_Now,A_MSec],this.XTOffset)
        if (this.Pause and (!this.SaveStrt[1] or this.OffsetSetting or this.ShiftStrt))
        {
          this.SaveStrt:=Strt
          this.ShiftStrt:=false
        }
        else if (!this.Pause and this.SaveStrt[1])
          this.SaveStrt:=&quot;&quot;
        if this.Pause
          Strt:=this.SaveStrt
        Endt:=Graph.TimeSubMs(Strt,this.ZoomT)
        ; линия и текст временой шкалы
        if (this.Sel=this.Proc)
        {
          loop,% this.GPosW
          {
            CoordX:=this.GPosW-(a_index-1)
            StampLine++
            if !isobject(this.StampPos)
              tmp1:=0
            else
              tmp1:=this.StampPos.W+2
            if (StampLine&gt;tmp1) or (!Stamp and a_index=this.GPosW)
            {
              Stamp:=true
              StampLine:=0
              this.DrawLine(this.GrayPen2,CoordX,0,CoordX,this.GPosH)
              tmp2:=(this.ZoomT/this.GPosW)*(a_index-1)
              tmp1:=Graph.TimeSubMs(Strt,tmp2)
              FormatTime,tmp10,% tmp1[1],dd.MM.yyyy&#039;`n&#039;HH:mm:ss
              tmp10.=&quot;.&quot; tmp1[2]
              StampPos:=this.DrawString(tmp10,CoordX-this.StampPos.W/2,this.GPosH,,,,this.Font_Colour,this.GrayPen2)
              if !GetSizeOnce
              {
                GetSizeOnce:=true
                this.StampPos:=StampPos
                this.StampOffset:=this.StampPos.H
              }
              if (prevStampPos.w)
              {
                if (this.StampPos.X+this.StampPos.w&gt;prevStampPos.X)
                  this.StampsNum--
                if (this.StampPos.X+this.StampPos.w*2+5&lt;prevStampPos.X)
                  this.StampsNum++
                if this.StampsNum&lt;2
                  this.StampsNum:=2
              }
              prevStampPos:=this.StampPos
            }
          }
        }
        ; найти первую точку
        if !this.GHide[this.Proc]
        loop,% ArrSize
        {
          i:=ArrSize-(a_index-1)-this.SearchSkip[this.Proc]
          if (i&lt;1)
          {
            iStart:=1
            break
          }
          if !this.TimeStampExist
          {
            iTime:=this.StampT[this.Proc,i,1]
            iTimeMs:=this.StampT[this.Proc,i,2]
          }
          else
          {
            iTime:=this.pData[i,2]
            if TimeStampMillis
              iTimeMs:=this.pData[i,3]
            else
            {
              if !Same
              {
                iTimeMs:=0
                loop,1000
                {
                  if (iTime=this.pData[i-a_index,2])
                    Same++
                  else
                  {
                    SameT:=1000/(Same+1)
                    break
                  }
                }
              }
              if (Same&gt;0)
              {
                iTimeMs:=round(SameT*Same)
                Same--
              }
            }
          }
          if (iTime&gt;Strt[1] or (TimeStampMillis and iTime=Strt[1] and iTimeMs&gt;=Strt[2]))
            continue
          else
          {
            iStart:=i=ArrSize?i:i+1
            break
          }
        }
        Old_CoordY:=ZeroPos
        Old_CoordX:=this.GPosW+10
        GPoints:=[[Old_CoordX,Old_CoordY]]
        SearchSkip:=this.SearchSkip[this.Proc]?this.SearchSkip[this.Proc]:0
        Same:=0
        Aindex:=0
        iLast:=false
        if !this.GHide[this.Proc]
        loop,% iStart
        {
          i:=iStart-(a_index-1)
          if !this.TimeStampExist
          {
            iTime:=this.StampT[this.Proc,i,1]
            iTimeMs:=this.StampT[this.Proc,i,2]
          }
          else
          {
            iTime:=this.pData[i,2]
            if TimeStampMillis
              iTimeMs:=this.pData[i,3]
            else
            {
              if !Same
              {
                iTimeMs:=0
                loop,1000
                {
                  tmp1:=this.pData[i-a_index,2]
                  if (iTime=tmp1)
                    Same++
                  else
                  {
                    SameT:=1000/(Same+1)
                    break
                  }
                }
              }
              if (Same&gt;0)
              {
                iTimeMs:=round(SameT*Same)
                Same--
              }
            }
          }
          if (iTime&lt;Endt[1])
            iLast:=true
          mms:=Strt[2]-iTimeMs
          if (mms&lt;0)
          {
            mms+=1000
            iTime+=1,Seconds
          }
          ttt:=Strt[1]
          ttt-=iTime,Seconds
          CoordX:=this.GPosW-round(PerioT*(ttt*1000+mms))
          Aindex++
          if (Aindex=1 and !this.SearchStop[this.Proc])
          {
            tmp1:=ArrSize-i
            if (SearchSkip&lt;tmp1)
              SearchSkip:=tmp1
          }
          if (CoordX!=Old_CoordX or iLast)
          {
            if !this.TimeStampExist
              Value:=this.pData[i]
            else
              Value:=this.pData[i,1]
            tmp1:=Value?Value:0
            if (Value!=&quot;&quot; and Aindex)
            {
              hi_lim:=Value&gt;hi_lim ? Value : hi_lim
              lo_lim:=Value&lt;lo_lim ? Value : lo_lim
            }
            Value:=Value!=&quot;&quot;?Value:&quot;Null&quot;
            CoordY:=ZeroPos-tmp1*Pixel
            SkipLine:=false
            if (CoordY&lt;-1)
            {
              CoordY:=0
              SkipLine:=true
              this.LockYP[this.Proc]++
            }
            else if (CoordY&gt;this.GPosH+1)
            {
              CoordY:=this.GPosH
              SkipLine:=true
              this.LockYM[this.Proc]++
            }
            ; линия графика
            if !this.Smooth[this.Proc]
            {
              GPoints.Push([Old_CoordX,CoordY])
              Old_CoordY:=CoordY
            }
            GPoints.Push([CoordX,CoordY])
            ; Отрисовка подробностей при малом увеличении
            if (Oldi-i&gt;1)
            {
              max:=Graph.min
              min:=Graph.max
              loop,% Oldi-i
              {
                j:=i+a_index-1
                if (j&lt;1 or j&gt;ArrSize)
                  continue
                if !this.TimeStampExist
                  tmp1:=this.pData[j]
                else
                  tmp1:=this.pData[j,1]
                min:=min&lt;tmp1?min:tmp1
                max:=max&gt;tmp1?max:tmp1
                if (tmp1!=&quot;&quot;)
                {
                  hi_lim:=tmp1&gt;hi_lim ? tmp1 : hi_lim
                  lo_lim:=tmp1&lt;lo_lim ? tmp1 : lo_lim
                }
              }
              min:=ZeroPos-min*Pixel
              max:=ZeroPos-max*Pixel
              if (min&lt;0)
                min:=1
              if (min&gt;this.GPosH)
                min:=this.GPosH
              if (max&lt;0)
                max:=0
              if (max&gt;this.GPosH)
                max:=this.GPosH-1
              GPoints.Push([CoordX,min])
              GPoints.Push([CoordX,max])
              GPoints.Push([CoordX,CoordY])
            }
            ; Дорисовать точки при большом увеличении
            if (Old_CoordX-CoordX&gt;3)
              this.DrawEllipse(this.Pen[this.Proc],CoordX-1,CoordY-1,2,2,this.LineW[this.Proc])

            ; текст значения под курсором
            if (!this.Val_UnderGraph_Drawed[this.Proc] and MouseIn)
            if (mX&gt;=CoordX and mX&lt;=CoordX+20)
            if (mY&gt;CoordY and mY&lt;CoordY+20)
            {
              this.Val_UnderGraph_Drawed[this.Proc]:=true
              this.UnderGX[this.Proc]:=CoordX
              this.UnderGY[this.Proc]:=CoordY
              this.UnderGV[this.Proc]:=Value
            }
            Old_CoordX:=CoordX
            Old_CoordY:=CoordY
            Oldi:=i
            if iLast
              break
            ; маркеры
            if (this.Sel=this.Proc)
            {
              loop,2
              {
                if (!XMarker%a_index% and CoordX&lt;this.XMarker[a_index])
                {
                  XMarker%a_index%:=true
                  XMarker%a_index%i:=i
                  if !TimeStampMillis
                    MarkerTimeMs%a_index%:=format(&quot;{:03}&quot;,iTimeMs)
                  XMarker%a_index%Coord:=CoordX
                }
              }
            }
          }
        }
        ; рисовать линию графика
        if !this.GHide[this.Proc]
          this.DrawLines(this.Pen[this.Proc],GPoints,this.LineW[this.Proc])
        this.ZoomX:=Aindex
        this.SearchSkip[this.Proc]:=SearchSkip

        ; блокировать перемещение по Y
        this.LockYP[this.Proc]:=(this.LockYP[this.Proc]&gt;=Aindex-3)?1:0
        this.LockYM[this.Proc]:=(this.LockYM[this.Proc]&gt;=Aindex-3)?1:0
      }
      ; Автомасштаб
      if (this.AutoScale[this.Proc] and !this.GHide[this.Proc])
      {
        tmp1:=this.GPosH//2
        if (this.ZeroPosU&gt;tmp1 and this.ZeroPosU&lt;this.GPosH+1)
        {
          if (hi_lim&gt;0)
          {
            tmp2:=this.GPosH-this.ZeroPosU
            this.YScale[this.Proc]:=abs(hi_lim)+tmp2/Pixel
          }
          else
            this.YScale[this.Proc]:=-lo_lim+this.ZeroPosU/Pixel
        }
        else if (this.ZeroPosU&lt;=tmp1 and this.ZeroPosU&gt;0)
        {
          if (lo_lim&lt;0)
            this.YScale[this.Proc]:=-lo_lim+this.ZeroPosU/Pixel
          else
          {
            tmp2:=this.GPosH-this.ZeroPosU
            this.YScale[this.Proc]:=abs(hi_lim)+tmp2/Pixel
          }
        }
        if (this.YScale[this.Proc]=0)
          this.YScale[this.Proc]:=1
      }
      this.DrawLine(this.GrayPen2,0,this.GPosH,this.GPosW,this.GPosH)
      this.DrawLine(this.GrayPen2,0,this.GPosH+StampPos.H,this.GPosW,this.GPosH+StampPos.H)

      ; текст значения под курсором
      if this.Val_UnderGraph_Drawed[this.Proc]
      {
        tmpX:=this.UnderGX[this.Proc]+5&lt;this.GPosW-this.ValTipPosW?this.UnderGX[this.Proc]+5:this.GPosW-this.ValTipPosW
        tmpY:=this.UnderGY[this.Proc]-this.Pos.H&lt;0?0:(this.UnderGY[this.Proc]-this.Pos.H-5)
        this.DrawEllipse(0xffffffff,this.UnderGX[this.Proc]-3,this.UnderGY[this.Proc]-3,6,6,this.LineW[this.Proc])
        this.DrawEllipse(0xff000000,this.UnderGX[this.Proc]-2,this.UnderGY[this.Proc]-2,4,4,this.LineW[this.Proc])
        this.DrawEllipse(0xffffffff,this.UnderGX[this.Proc]-1,this.UnderGY[this.Proc]-1,2,2,this.LineW[this.Proc])
        tmpc:=Graph.AHSVToARGB(200,this.Hue[this.Proc],this.Hue[this.Proc]&gt;255?0:100,this.Hue[this.Proc]&gt;255?(this.Hue[this.Proc]&gt;&gt;8):255)
        this.ValTipPos:=this.DrawString(this.UnderGV[this.Proc],tmpX,tmpY,,,,this.Font_Colour,tmpc)
        this.ValTipPosW:=this.ValTipPos.W
      }
      if (this.Sel=this.Proc)
      {
        loop,2
        {
          if XMarker%a_index%Coord
          {
            if !this.TimeStampExist
            {
              MarkerTime%a_index%:=this.StampT[this.Proc,XMarker%a_index%i,1]
              FormatTime,tmp10,% MarkerTime%a_index%,HH:mm:ss
              MarkerTimeMs%a_index%:=this.StampT[this.Proc,XMarker%a_index%i,2]
              MarkerTimeFormated%a_index%:=tmp10 &quot;.&quot; MarkerTimeMs%a_index%
              MarkerValue%a_index%:=this.pData[XMarker%a_index%i]
              MarkerText%a_index%.=tmp10 &quot;.&quot; MarkerTimeMs%a_index% &quot; &quot; MarkerValue%a_index% &quot;&quot;
            }
            else
            {
              MarkerTime%a_index%:=this.pData[XMarker%a_index%i,2]
              FormatTime,tmp10,% MarkerTime%a_index%,dd.MM.yyyy HH:mm:ss
              if (TimeStampMillis and this.pData[XMarker%a_index%i,3]!=&quot;&quot;)
                MarkerTimeMs%a_index%:=this.pData[XMarker%a_index%i,3]
              tmp10.=&quot;.&quot; MarkerTimeMs%a_index%
              MarkerTimeFormated%a_index%:=tmp10
              MarkerValue%a_index%:=this.pData[XMarker%a_index%i,1]

              MarkerText%a_index%.=tmp10 &quot; &quot; this.pData[XMarker%a_index%i,1] &quot;&quot;
            }
          }
        }
      }
    } ; Конец цикла
    if (Graph.HotKeymC=this.hGPos)
    {
      Graph.WheelUp:=false
      Graph.WheelDown:=false
    }

; Отрисовать меню
    OnGraphMenu:
    MenuSPY:=StampPos.Y+StampPos.H
    SelPos:=[]
    tmpc:=Graph.AHSVToARGB(200,this.Hue[1],this.Hue[1]&gt;255?0:100,this.Hue[1]&gt;255?(this.Hue[1]&gt;&gt;8):255)
    SelPos[1]:=this.DrawString(&quot;1&quot;,0,MenuSPY,,,&quot;Center vCenter&quot;,this.GHide[1]?tmpc:this.Font_Colour,tmpc)
    loop,% Ars-1
    {
      tmp1:=a_index+1
      tmpc:=Graph.AHSVToARGB(200,this.Hue[tmp1],this.Hue[tmp1]&gt;255?0:100,this.Hue[tmp1]&gt;255?(this.Hue[tmp1]&gt;&gt;8):255)
      SelPos[tmp1]:=this.DrawString(tmp1,SelPos[a_index].X+SelPos[a_index].W+5,MenuSPY,,,&quot;Center vCenter&quot;,this.GHide[tmp1]?tmpc:this.Font_Colour,tmpc)
    }
    MenuSPY:=StampPos.Y+StampPos.H+SelPos[1].H
    if !this.ScaleXByTime
      this.ZoomPos:=this.DrawString(&quot;Zoom X = &quot; this.ZoomX &quot; точек&quot;,0,MenuSPY,,,&quot;&quot;,this.Font_Colour,this.LightBlueBrush)
    else
    {
      tmp1:=Graph.MsToTime(this.ZoomT)
      tmp1[1]-=1000000
      FormatTime,tmp2,% tmp1[1],dd HH:mm:ss
      tmp2.=&quot;.&quot; tmp1[2]
      this.ZoomPos:=this.DrawString(&quot;Zoom X = &quot; tmp2,0,MenuSPY,,,&quot;&quot;,this.Font_Colour,this.LightBlueBrush)
    }
    this.SmoothPos:=this.DrawString(&quot;Сглаживание&quot;,this.ZoomPos.W+this.Pos.W,MenuSPY,,,&quot;&quot;,this.Smooth[this.Sel]?this.Font_Colour:this.GrayPen,this.LightBlueBrush)
    this.LineWPos:=this.DrawString(&quot;Толщина &quot; this.LineW[this.Sel],this.SmoothPos.X+this.SmoothPos.W+this.Pos.W,MenuSPY,,,&quot;&quot;,this.Font_Colour,this.LightBlueBrush)
    this.FontSizePos:=this.DrawString(&quot;Шрифт &quot; this.FontSize,this.LineWPos.X+this.LineWPos.W+this.Pos.W,MenuSPY,,,&quot;&quot;,this.Font_Colour,this.LightBlueBrush)
    ; следующая строка
    this.AutoPos:=this.DrawString(&quot;Авто&quot;,0,this.ZoomPos.Y+this.ZoomPos.H,,,&quot;&quot;,this.AutoScale[this.Sel]?this.Font_Colour:this.GrayPen,this.LightBlueBrush)
    this.ScalePos:=this.DrawString(&quot;Шкала &quot; this.YScale[this.Sel],this.AutoPos.X+this.AutoPos.W,this.AutoPos.Y,,,&quot;&quot;,this.Font_Colour,this.LightBlueBrush)
    ; Размер меню
    tmp1:=(this.AutoPos.H&gt;this.ScalePos.H)?this.AutoPos.H:this.ScalePos.H
    this.MenuOffset:=this.ZoomPos.H+tmp1+SelPos[1].H

    ; хитбоксы
    loop,% Ars
    {
      SelHB%a_index%:=Graph.HitBox(mX,mY,SelPos[a_index])
      if (SelHB%a_index%)
      {
        SelHB:=true
        break
      }
    }
    LineWHB:=Graph.HitBox(mX,mY,this.LineWPos)
    FontSizeHB:=Graph.HitBox(mX,mY,this.FontSizePos)
    ZoomHB:=Graph.HitBox(mX,mY,this.ZoomPos)
    SmoothHB:=Graph.HitBox(mX,mY,this.SmoothPos)
    AutoHB:=Graph.HitBox(mX,mY,this.AutoPos)
    ScaleHB:=Graph.HitBox(mX,mY,this.ScalePos)
    HB:=AutoHB or ScaleHB or ZoomHB or SmoothHB or LineWHB or FontSizeHB or SelHB
    if (MouseIn and HB and !this.OffsetSetting and !this.Setting and A_Cursor=&quot;Arrow&quot;)
    {
      this.Setting:=true
      if AutoHB
      {
        TipText:=&quot;Автоматический масштаб`n`n Нажмите для включения/отключения&quot;
        this.WhichSetting:=1
      }
      else if ScaleHB
      {
        TipText:=&quot;Масштаб шкалы`n`nЗажмите левую `nкн. мыши и потяните`n для изменения&quot;
        this.WhichSetting:=2
      }
      else if ZoomHB
      {
        TipText:=&quot;Зажмите левую `nкн. мыши и потяните`n для изменения&quot;
        this.WhichSetting:=3
      }
      else if LineWHB
      {
        TipText:=&quot;Толщина линии графика`n`nЗажмите левую `nкн. мыши и потяните`n для изменения&quot;
        this.WhichSetting:=4
      }
      else if SelHB
      {
        TipText:=&quot;Нажмите левой кн. мыши чтобы`n выбрать график`n настройки которого`n будут изменяться`n`nПравой кн. мыши`n выключить график&quot;
        this.WhichSetting:=5
      }
      else if FontSizeHB
      {
        TipText:=&quot;Размер шрифта`n`nЗажмите левую `nкн. мыши и потяните`n для изменения&quot;
        this.WhichSetting:=6
      }
      else if SmoothHB
      {
        TipText:=&quot;Сглаживать ступеньки графика`n`n Нажмите для включения/отключения&quot;
        this.WhichSetting:=7
      }
      this.TipPos:=this.DrawString(TipText,(this.GPosW-this.TipPos.W)//2,this.GPosH-this.TipPos.H,,,,this.Font_Colour,this.LightGreenBrush)
    }
    ; Изменение настроек
    if (getkeystate(&quot;LButton&quot;,&quot;P&quot;) and this.Setting)
    {
      if (this.WhichSetting=1)
      {
        this.AutoScale[this.Sel]:=!this.AutoScale[this.Sel]
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=2)
      {
        this.YScale[this.Sel]+=(round(abs(this.YScale[this.Sel])/20)+1)*DeltaY
        this.YScale[this.Sel]:=round(this.YScale[this.Sel])
        this.AutoScale[this.Sel]:=false
      }
      else if (this.WhichSetting=3)
      {
        this.ZoomX+=(round(abs(this.ZoomX)/20)+1)*DeltaY
        this.ZoomX:=(this.ZoomX&gt;100000)?100000:(this.ZoomX&lt;10)?10:round(this.ZoomX)
        this.ZoomT+=(round(abs(this.ZoomT)/20)+1)*DeltaY
        this.ZoomT:=(this.ZoomT&gt;Graph.MaxTime)?Graph.MaxTime:(this.ZoomT&lt;1000)?1000:round(this.ZoomT)
      }
      else if (this.WhichSetting=4)
      {
        this.LineW[this.Sel]+=DeltaY/10
        if (this.LineW[this.Sel]&lt;1)
          this.LineW[this.Sel]:=1
        if (this.LineW[this.Sel]&gt;5)
          this.LineW[this.Sel]:=5
        this.LineW[this.Sel]:=round(this.LineW[this.Sel],1)
      }
      else if (this.WhichSetting=5)
      {
        loop,% Ars
        {
          if (SelHB%a_index%)
          {
            this.Sel:=a_index
            this.UpdateMenu()
            break
          }
        }
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=6)
      {
        this.FontSize+=DeltaY
        if (this.FontSize&lt;8)
          this.FontSize:=8
        if (this.FontSize&gt;70)
          this.FontSize:=70
        if (this.FontSize!=this.prevSize)
          this.SetFont(,this.FontSize)
      }
      else if (this.WhichSetting=7)
      {
        this.Smooth[this.Sel]:=!this.Smooth[this.Sel]
        this.WhichSetting:=0
      }
    }
    if (getkeystate(&quot;RButton&quot;,&quot;P&quot;) and this.Setting)
    {
      if (this.WhichSetting=5)
      {
        loop,% Ars
        {
          if (SelHB%a_index%)
          {
            this.GHide[a_index]:=!this.GHide[a_index]
            break
          }
        }
        this.WhichSetting:=0
      }
    }
    if (!getkeystate(&quot;LButton&quot;,&quot;P&quot;) and !getkeystate(&quot;RButton&quot;,&quot;P&quot;))
    {
      this.Setting:=false
      this.WhichSetting:=0
    }

; Информация от маркеров
    Markers:
    max:=min:=&quot;`n&quot;
    if this.ShowMarkers
    {
      loop,2
      {
        if XMarker%a_index%Coord
          this.DrawLine(this.RedPen,XMarker%a_index%Coord,0,XMarker%a_index%Coord,this.GPosH)
      }
      if !XMarker1Coord
        MarkerText1:=&quot;Нажмите левой кн. мыши на график&quot;
      if !XMarker2Coord
        MarkerText2:=&quot;Нажмите правой кн. мыши на график&quot;
      if (XMarker1Coord and XMarker2Coord)
      {
        i:=1, j:=2
        if (XMarker1Coord&lt;XMarker2Coord)
          i:=2, j:=1
        tmp1:=Graph.TimeToMs(MarkerTime%i%)+MarkerTimeMs%i%
        tmp2:=Graph.TimeToMs(MarkerTime%j%)+MarkerTimeMs%j%
        ;YYYYMMDDHH24MISS
        MarkerTime%i%-=MarkerTime%j%,Seconds
        MarkerDeltaT:=1601
        MarkerDeltaT+=MarkerTime%i%,Seconds
        FormatTime,Y,% MarkerDeltaT,yyyy
        FormatTime,M,% MarkerDeltaT,MM
        FormatTime,D,% MarkerDeltaT,dd
        Y:=format(&quot;{:04}&quot;,Y-1601)
        M:=format(&quot;{:02}&quot;,M-1)
        D:=format(&quot;{:02}&quot;,D-1)
        if this.TimeStampExist
          MarkerDText.=D &quot;.&quot; M &quot;.&quot; Y &quot; &quot;

        tmp3:=Graph.MsToTime(tmp1-tmp2)
        tmp3:=tmp3[1]
        FormatTime,tmp10,% tmp3,HH:mm:ss
        MarkerDText.=tmp10

        if (MarkerTimeMs%i%!=&quot;&quot;)
        {
          StringRight,tmp1,% (tmp1-tmp2),3
          MarkerDText.=&quot;.&quot; tmp1
        }

        max:=Graph.min
        min:=Graph.max
        ValNum:=XMarker%i%i-XMarker%j%i+1
        loop,% ValNum
        {
          if !this.TimeStampExist
            Val:=this.pData[XMarker%j%i+A_Index-1]
          else
            Val:=this.pData[XMarker%j%i+A_Index-1,1]
          Summ+=Val
          if (Val&gt;max)
            max:=Val
          if (Val&lt;min)
            min:=Val
        }
        Mean:=Summ/ValNum
        loop,% ValNum
        {
          if !this.TimeStampExist
            Val:=this.pData[XMarker%j%i+A_Index-1]
          else
            Val:=this.pData[XMarker%j%i+A_Index-1,1]
          Deviation+=(Val-Mean)**2
        }
        StDeviation:=sqrt((Deviation/ValNum))
        StDeviation:=&quot;Среднекв. отклонение = &quot; StDeviation
        Mean:=&quot;Среднее = &quot; Mean
        min:=&quot;Min = &quot; min
        max:=&quot;Max = &quot; max
        ValNum:=&quot;Точек = &quot; ValNum
      }
      String:=MarkerText1 &quot;`n&quot; MarkerText2 &quot;`n&quot; MarkerDText &quot;`n&quot; StDeviation &quot;`n&quot; Mean &quot;`n&quot; max &quot;`n&quot; min &quot;`n&quot; ValNum
      Pos:=this.DrawString(String,0,this.AutoPos.Y+this.AutoPos.H,this.GPosW,,&quot;&quot;,this.Font_Colour,this.LightGreenBrush)
    }

; Подсказка о текущем увеличении
    if ((!this.ScaleXByTime and this.OldZoomX!=this.ZoomX) or this.OldZoomT!=this.ZoomT)
    {
      this.ShowZoomTip:=1000
      this.OldZoomX:=this.ZoomX
      this.OldZoomT:=this.ZoomT
    }
    if (this.ShowZoomTip&gt;0)
    {
      this.ShowZoomTip-=CycleTime
      if !this.ScaleXByTime
        this.iZoomPos:=this.DrawString(&quot;Zoom`n&quot; this.ZoomX &quot;`nточек&quot;,(this.GPosW-this.iZoomPos.W)//2,0,,,,this.Font_Colour,this.LightGreenBrush)
      else
      {
        tmp1:=Graph.MsToTime(this.ZoomT)
        tmp1[1]+=-1000000
        FormatTime,tmp2,% tmp1[1],dd HH:mm:ss
        tmp2.=&quot;.&quot; tmp1[2]
        this.iZoomPos:=this.DrawString(&quot;Zoom`n&quot; tmp2,(this.GPosW-this.iZoomPos.W)//2,0,,,,this.Font_Colour,this.LightGreenBrush)
      }
    }
; Подсказка о времени отрисовки
    DllCall(&quot;QueryPerformanceCounter&quot;,&quot;Int64*&quot;,EndTime)
    ElapsedTime:=round((EndTime-StartTime)/frequency*1000,1)
    this.ETPos:=this.DrawString(ElapsedTime &quot;ms&quot;,this.GPosW-this.ETPos.W,this.ZoomPos.Y,,,,this.Font_Colour,this.Font_Back)
    if (Graph.HitBox(mX,mY,this.ETPos) and MouseIn)
      this.TipPos:=this.DrawString(&quot;Время отрисовки кадра&quot;,(this.GPosW-this.TipPos.W)//2,this.GPosH-this.TipPos.H,,,,this.Font_Colour,this.LightGreenBrush)

    if (ElapsedTime&gt;1000)
    {
      if !this.ScaleXByTime
        this.ZoomX-=round(abs(this.ZoomX)/5)
      else
        this.ZoomT-=round(abs(this.ZoomT)/5)
    }

; Обновить изображение
    if !this.GuiHiden
      this.Update()
    this.OldmX:=mX
    this.OldmY:=mY
    this.FloatFormatFix()
  }
;||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/||||
;|||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||
;||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////||
;|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|
;/////////////////////////////////////////////////////////////////////////////////////////////////

  ConstrainXOffset()
  {
    if !this.ScaleXByTime
    {
      tmp1:=this.ZoomX//2
;      if (this.X_Offset&gt;this.ArrSize[this.Proc]-tmp1)
;        this.X_Offset:=this.ArrSize[this.Proc]-tmp1
      if (this.X_Offset&lt;-tmp1)
        this.X_Offset:=-tmp1
    }
    else
    {
      tmp1:=this.ZoomT//2
      if this.TimeStampExist
        tmp2:=Graph.TimeSub([A_Now,A_MSec],[this.pData[1,2],this.pData[1,3]])
      else
        tmp2:=Graph.TimeSub([A_Now,A_MSec],[this.StampT[this.Proc,1,1],this.StampT[this.Proc,1,2]])
;      if (this.XTOffset&gt;tmp2-tmp1)
;        this.XTOffset:=tmp2-tmp1
      if (this.XTOffset&lt;-tmp1)
        this.XTOffset:=-tmp1
    }
  }

  TimeSub(Time1,Time2)
  {
    T1:=Time1[1]
    T2:=Time2[1]
    ms:=Time1[2]-Time2[2]
    if (ms&lt;0)
    {
      ms+=1000
      T2+=1,Seconds
    }
    T1-=T2,Seconds
    return T1*1000+ms
  }
  TimeSubMs(Time,ValueMs)
  {
    if (ValueMs=0)
      return Time
    m:=0
    if (ValueMs&gt;0)
    {
      ms:=Time[2]-mod(ValueMs,1000)
      if (ms&lt;0)
      {
        ms+=1000
        m:=1
      }
      t:=Time[1]
      t+=-(Floor(ValueMs/1000)+m),Seconds
    }
    else
    {
      ms:=mod(-ValueMs,1000)+Time[2]
      if (ms&gt;999)
      {
        ms-=1000
        m:=1
      }
      t:=Time[1]
      t+=(Floor(-ValueMs/1000)+m),Seconds
    }
    return [t,format(&quot;{:03}&quot;,round(ms))]
  }
  TimeAddMs(Time,ValueMs)
  {
    T:=Time[1]
    T+=ValueMs//1000,Seconds
    ms:=Time[2]+mod(ValueMs,1000)
    if (ms&gt;999)
    {
      ms-=1000
      T+=1,Seconds
    }
    return [T,format(&quot;{:03}&quot;,round(ms))]
  }
  TimeToMs(Time)
  {
    FormatTime,HH,% Time,HH
    FormatTime,MM,% Time,mm
    FormatTime,SS,% Time,ss
    return HH*3600000+MM*60000+SS*1000
  }
  MsToTime(Ms)
  {
    T:=1601
    T+=Ms//1000,Seconds
    Ms:=format(&quot;{:03}&quot;,mod(Ms,1000))
    return [T,Ms]
  }

  TTOk()
  {
    Gui,% this.hGuiTT . &quot;:submit&quot;
    GuiControlGet,ToTime,,% this.hTT
    tmp1:=A_Now
    tmp1-=ToTime,Seconds
    this.XTOffset:=tmp1*1000-round((this.GPosW/2)/this.PerioT)
    this.ConstrainXOffset()
    this.Pause:=true
    this.ShiftStrt:=true
  }
  TTCancel()
  {
    Gui,% this.hGuiTT . &quot;:hide&quot;
  }
  TXOk()
  {
    Gui,% this.hGuiTX . &quot;:submit&quot;
    GuiControlGet,ToX,,% this.hTX
    this.X_Offset:=this.ArrSize[this.Sel]-ToX
    this.ConstrainXOffset()
    this.Pause:=true
  }
  TXCancel()
  {
    Gui,% this.hGuiTX . &quot;:hide&quot;
  }

  HelpClose()
  {
    Gui,% this.hGuiHelp . &quot;:hide&quot;
  }

  MenuGColor()
  {
    tmp1:=Graph.ChooseColor(this.Pen[this.Sel],this.hGuiB)
    if (tmp1!=&quot;&quot;)
    {
      tmp2:=Graph.RGBToHSV(tmp1)
      if ((tmp2&gt;&gt;8)&amp;0xff=0)
      {
        tmp3:=(tmp2&amp;0xff)
        if (tmp3&lt;127)
          tmp3:=127
        this.Hue[this.Sel]:=tmp3&lt;&lt;8
      }
      else
        this.Hue[this.Sel]:=tmp2&gt;&gt;16
      this.Pen[this.Sel]:=0xff000000 | tmp1
    }
  }
  MenuHelp()
  {
    hGuiHelp:=this.hGuiHelp
    Gui,%hGuiHelp%:show,,Справка
  }
  MenuGoTo()
  {
    if this.ScaleXByTime
    {
      hGuiTT:=this.hGuiTT
      Gui,%hGuiTT%:show,,Перейти на время
    }
    else
    {
      hGuiTX:=this.hGuiTX
      Gui,%hGuiTX%:show,,Перейти к точке
    }
  }
  MenuClose()
  {
    this.Hide()
  }
  MenuPause()
  {
    this.Pause:=!this.Pause
  }
  MenuForward()
  {
    this.Pause:=false
    this.XTOffset:=0
    this.X_Offset:=0
    this.ShiftStrt:=true
  }
  MenuBackward()
  {
    this.Pause:=true
    this.X_Offset:=this.ArrSize[this.Sel]-this.ZoomX//2
    if this.ScaleXByTime
    {
      tmp1:=A_Now
      if this.TimeStampExist
        tmp1-=this.pData[1,2],Seconds
      else
        tmp1-=this.StampT[this.Sel,1,1],Seconds
      this.XTOffset:=tmp1*1000-this.ZoomT//2
    }
    this.ShiftStrt:=true
  }
  MenuMarkers()
  {
    Add:=this.Pos.H*8
    if this.ShowMarkers:=!this.ShowMarkers
    {
      Size:=100+Add
      Number:=this.Number
      Gui,Graph%Number%:+MinSize300x%Size%
      WinGetPos,,,,BPosH,% &quot;ahk_id&quot; this.hGuiB
      WinMove,% &quot;ahk_id&quot; this.hGuiB,,,,,BPosH+Add
      this.MarkerOffset:=Add
    }
    else
    {
      Number:=this.Number
      Gui,Graph%Number%:+MinSize300x100
      WinGetPos,,,,BPosH,% &quot;ahk_id&quot; this.hGuiB
      WinMove,% &quot;ahk_id&quot; this.hGuiB,,,,,BPosH-Add
      this.MarkerOffset:=0
    }
  }
  MenuAutoScale()
  {
    if (this.Lock_hi_lim[this.Sel] or this.Lock_lo_lim[this.Sel])
      this.AutoScale[this.Sel]:=false
    Number:=this.Number
    if this.AutoScale[this.Sel]:=!this.AutoScale[this.Sel]
    {
      this.Lock_hi_lim[this.Sel]:=false
      this.Lock_lo_lim[this.Sel]:=false
      menu,GraphMenu%Number%settings,check,Автомасштаб
    }
    else
      menu,GraphMenu%Number%settings,uncheck,Автомасштаб
  }
  MenuAlwaysOnTop()
  {
    Number:=this.Number
    if this.AlwaysOnTop:=!this.AlwaysOnTop
    {
      Gui,Graph%Number%:+alwaysontop
      menu,GraphMenu%Number%settings,check,Всегда поверх окон
    }
    else
    {
      Gui,Graph%Number%:-alwaysontop
      menu,GraphMenu%Number%settings,uncheck,Всегда поверх окон
    }
  }
  MenuSmooth()
  {
    Number:=this.Number
    if this.Smooth[this.Sel]:=!this.Smooth[this.Sel]
      menu,GraphMenu%Number%settings,check,Сглаживать ступеньки
    else
      menu,GraphMenu%Number%settings,uncheck,Сглаживать ступеньки
  }
  MenuScaleXByTime()
  {
    Number:=this.Number
    this.ScaleXByTime:=true
    menu,GraphMenu%Number%settings,check,Масштабировать X по времени
    menu,GraphMenu%Number%settings,uncheck,Масштабировать X по точкам
  }
  MenuScaleXByDots()
  {
    Number:=this.Number
    this.ScaleXByTime:=false
    menu,GraphMenu%Number%settings,uncheck,Масштабировать X по времени
    menu,GraphMenu%Number%settings,check,Масштабировать X по точкам
  }
  MenuResetFont()
  {
    this.FontSize:=12
    this.SetFont(,this.FontSize)
  }
  UpdateMenu()
  {
    Number:=this.Number
    if this.AutoScale[this.Sel]
      menu,GraphMenu%Number%settings,check,Автомасштаб
    else
      menu,GraphMenu%Number%settings,uncheck,Автомасштаб
    if this.Smooth[this.Sel]
      menu,GraphMenu%Number%settings,check,Сглаживать ступеньки
    else
      menu,GraphMenu%Number%settings,uncheck,Сглаживать ступеньки
  }

  AHSVToARGB(a=255,h=0,s=0,v=0)
  {
    H:=h/255
    S:=s/255
    V:=v/255
    i:=Floor(H*6)
    f:=H*6-i
    p:=V*(1-S)
    q:=V*(1-f*S)
    t:=V*(1-(1-f)*S)
    sw:=mod(i,6)
    if (sw=0)
      r:=V, g:=t, b:=p
    if (sw=1)
      r:=q, g:=V, b:=p
    if (sw=2)
      r:=p, g:=V, b:=t
    if (sw=3)
      r:=p, g:=q, b:=V
    if (sw=4)
      r:=t, g:=p, b:=V
    if (sw=5)
      r:=V, g:=p, b:=q
    r:=(r*255)&amp;0xff
    g:=(g*255)&amp;0xff
    b:=(b*255)&amp;0xff
    return (a&lt;&lt;24 | r&lt;&lt;16 | g&lt;&lt;8 | b)
  }
  RGBToHSV(RGB)
  {
    r:=(RGB&gt;&gt;16 &amp; 0xff)/255
    g:=(RGB&gt;&gt;8 &amp; 0xff)/255
    b:=(RGB &amp; 0xff)/255
    min:=(r&lt;g)?((r&lt;b)?r:b):((g&lt;b)?g:b)
    val:=(r&gt;g)?((r&gt;b)?r:b):((g&gt;b)?g:b)
    delta:=val-min
    if (delta=0) ;pure blackness
      return (RGB &amp; 0xff)
    if (r=val) ;between yellow and magenta
      hue:=(g-b)/delta
    else if (g=val) ;between cyan and yellow
      hue:=2+(b-r)/delta
    else ;between magenta and cyan
      hue:=4+(r-g)/delta
    if (hue&lt;=0)
      hue+=6
    hue:=round(hue/6*255)
    sat:=round((Delta/val)*255)
    val:=round(val*255)
    return (hue&lt;&lt;16 | sat&lt;&lt;8 | val)
  }
  Show(x=&quot;&quot;,y=&quot;&quot;)
  {
    this.GuiHiden:=false
    hGuiB:=this.hGuiB
    if (x=&quot;&quot;)
      x:=a_screenwidth//2-400
    if (y=&quot;&quot;)
      y:=a_screenheight//2-200
    Gui,%hGuiB%:show,% &quot;x&quot; x &quot; y&quot; y,% this.Name
    obm:=this.UpdateTimer
    settimer,% obm,% this.UpdatePeriod
  }

  Hide()
  {
    this.GuiHiden:=true
    obm:=this.UpdateTimer
    settimer,% obm,off
    this.Update(0)
    Gui,% this.hGuiB . &quot;:hide&quot;
  }

  Create()
  {
    this.hDC:=DllCall(&quot;CreateCompatibleDC&quot;,&quot;UPtr&quot;,0)
    VarSetCapacity(bi,40,0)
    NumPut(40,bi,0,&quot;uint&quot;)
    NumPut(A_ScreenWidth,bi,4,&quot;uint&quot;)
    NumPut(A_ScreenHeight,bi,8,&quot;uint&quot;)
    NumPut(1,bi,12,&quot;ushort&quot;)
    NumPut(32,bi,14,&quot;ushort&quot;)
    NumPut(0,bi,16,&quot;uInt&quot;)
    this.hBitmap:=DllCall(&quot;CreateDIBSection&quot;
                     ,&quot;Ptr&quot;,this.hDC
                     ,&quot;Ptr&quot;,&amp;bi
                     ,&quot;uint&quot;,0
                     ,&quot;UPtr*&quot;,0
                     ,&quot;Ptr&quot;,0
                     ,&quot;uint&quot;,0,&quot;Ptr&quot;)
    this.obm:=DllCall(&quot;SelectObject&quot;,&quot;Ptr&quot;,this.hDC,&quot;Ptr&quot;,this.hBitmap)
    DllCall(Graph.hGdipCreateFromHDC,&quot;Ptr&quot;,this.hDC,&quot;UPtr*&quot;,pGraphics)
    this.pGraphics:=pGraphics
    DllCall(Graph.hGdipSetSmoothingMode,&quot;UPtr&quot;,this.pGraphics,&quot;Int&quot;,4)
  }

  Update(Alpha=255)
  {
    wingetpos,,,BPosW,BPosH,% &quot;ahk_id&quot; this.hGuiB
    if (this.PrevW!=BPosW or this.PrevH!=BPosH)
    {
      Pos:=Graph.GetClientSize(this.hGuiB)
      WinMove,% &quot;ahk_id&quot; this.hGPos,,0,0,Pos.W-1,Pos.H-1
      this.PrevW:=BPosW
      this.PrevH:=BPosH
    }
    wingetpos,GPosX,GPosY,GPosW,GPosH,% &quot;ahk_id&quot; this.hGPos
    this.GPosW:=GPosW
    this.GPosH:=GPosH-this.StampOffset-this.MarkerOffset-this.MenuOffset
    this.GPosH_:=GPosH
    this.ZeroPosU:=this.GPosH-this.ZeroPos
    VarSetCapacity(pt,8)
    NumPut(GPosX,pt,0,&quot;UInt&quot;)
    NumPut(GPosY,pt,4,&quot;UInt&quot;)
    DllCall(&quot;UpdateLayeredWindow&quot;
            ,&quot;Ptr&quot;,this.hGuiG
            ,&quot;Ptr&quot;,0
            ,&quot;Ptr&quot;,&amp;pt
            ,&quot;int64*&quot;,GPosW|GPosH&lt;&lt;32
            ,&quot;Ptr&quot;,this.hDC
            ,&quot;int64*&quot;,0
            ,&quot;uint&quot;,0
            ,&quot;UInt*&quot;,Alpha&lt;&lt;16|1&lt;&lt;24
            ,&quot;uint&quot;,2)
  }

  GetClientSize(hwnd)
  {
    VarSetCapacity(rc,16)
    DllCall(&quot;GetClientRect&quot;,&quot;uint&quot;,hwnd,&quot;uint&quot;,&amp;rc)
    return {&quot;X&quot;:NumGet(rc,0,&quot;int&quot;),&quot;Y&quot;:NumGet(rc,4,&quot;int&quot;),&quot;W&quot;:NumGet(rc,8,&quot;int&quot;),&quot;H&quot;:NumGet(rc,12,&quot;int&quot;)}
  }

  CreateSolidBrush(ARGB=0xff000000)
  {
    DllCall(Graph.hGdipCreateSolidFill,&quot;UInt&quot;,ARGB,&quot;UPtr*&quot;,pBrush)
    return pBrush
  }
  DeleteBrush(byref pBrush)
  {
    DllCall(Graph.hGdipDeleteBrush,&quot;UPtr&quot;,pBrush)
    pBrush:=&quot;&quot;
  }
  CreatePen(ARGB=0xff000000,w=1)
  {
    DllCall(Graph.hGdipCreatePen1,&quot;UInt&quot;,ARGB,&quot;float&quot;,w,&quot;int&quot;,2,&quot;UPtr*&quot;,pPen)
    return pPen
  }
  DeletePen(byref pPen)
  {
    DllCall(Graph.hGdipDeletePen,&quot;UPtr&quot;,pPen)
    pPen:=&quot;&quot;
  }

  FillRectangle(ARGB,x,y,w,h)
  {
    if (!this.pBrush or this.prevBrushColor!=ARGB)
    {
      if this.pBrush
        Graph.DeleteBrush(this.pBrush)
      this.pBrush:=Graph.CreateSolidBrush(ARGB)
    }
    this.prevBrushColor:=ARGB
    DllCall(Graph.hGdipFillRectangle
            ,&quot;Ptr&quot;,this.pGraphics
            ,&quot;Ptr&quot;,this.pBrush
            ,&quot;float&quot;,x
            ,&quot;float&quot;,y
            ,&quot;float&quot;,w
            ,&quot;float&quot;,h)
    return {&quot;X&quot;:x,&quot;Y&quot;:y,&quot;W&quot;:w,&quot;H&quot;:h}
  }
  DrawLine(ARGB,x1,y1,x2,y2,LineWidth=1)
  {
    if (!this.pPen or this.prevPenColor!=ARGB or this.OldLineWidth!=LineWidth)
    {
      if this.pPen
        Graph.DeletePen(this.pPen)
      this.pPen:=Graph.CreatePen(ARGB,LineWidth)
    }
    this.OldLineWidth:=LineWidth
    this.prevPenColor:=ARGB
    DllCall(Graph.hGdipDrawLine
            ,Ptr,this.pGraphics
            ,Ptr,this.pPen
            ,&quot;float&quot;,x1
            ,&quot;float&quot;,y1
            ,&quot;float&quot;,x2
            ,&quot;float&quot;,y2)
    return {&quot;X1&quot;:x1,&quot;Y1&quot;:y1,&quot;X2&quot;:x2,&quot;Y2&quot;:y2}
  }
  DrawLines(ARGB,Points,LineWidth=1)
  {
    tmp1:=Points.MaxIndex()
    if !tmp1
      return -1
    VarSetCapacity(PointF,8*tmp1)
    Loop,% tmp1
    {
      NumPut(Points[a_index,1],PointF,8*(A_Index-1),&quot;float&quot;)
      NumPut(Points[a_index,2],PointF,(8*(A_Index-1))+4,&quot;float&quot;)
    }

    if (!this.pPen or this.prevPenColor!=ARGB or this.OldLineWidth!=LineWidth)
    {
      if this.pPen
        Graph.DeletePen(this.pPen)
      this.pPen:=Graph.CreatePen(ARGB,LineWidth)
    }
    this.OldLineWidth:=LineWidth
    this.prevPenColor:=ARGB
    return DllCall(Graph.hGdipDrawLines
                   ,&quot;Ptr&quot;,this.pGraphics
                   ,&quot;Ptr&quot;,this.pPen
                   ,&quot;Ptr&quot;,&amp;PointF
                   ,&quot;int&quot;,tmp1)
  }
  DrawEllipse(ARGB,x,y,w,h,LineWidth=1)
  {
    if (!this.pPen or this.prevPenColor!=ARGB or this.OldLineWidth!=LineWidth)
    {
      if this.pPen
        Graph.DeletePen(this.pPen)
      this.pPen:=Graph.CreatePen(ARGB,LineWidth)
    }
    this.OldLineWidth:=LineWidth
    this.prevPenColor:=ARGB
    DllCall(Graph.hGdipDrawEllipse
              ,&quot;Ptr&quot;,this.pGraphics
              ,&quot;Ptr&quot;,this.pPen
              ,&quot;float&quot;,x
              ,&quot;float&quot;,y
              ,&quot;float&quot;,w
              ,&quot;float&quot;,h)
    return {&quot;X&quot;:x,&quot;Y&quot;:y,&quot;W&quot;:w,&quot;H&quot;:h}
  }
  DrawRectangle(ARGB,x,y,w,h,LineWidth=1)
  {
    if (!this.pPen or this.prevPenColor!=ARGB or this.OldLineWidth!=LineWidth)
    {
      if this.pPen
        Graph.DeletePen(this.pPen)
      this.pPen:=Graph.CreatePen(ARGB,LineWidth)
    }
    this.OldLineWidth:=LineWidth
    this.prevPenColor:=ARGB
    DllCall(Graph.hGdipDrawRectangle
                ,&quot;Ptr&quot;,this.pGraphics
                , &quot;Ptr&quot;,this.pPen
                , &quot;float&quot;,x
                , &quot;float&quot;,y
                , &quot;float&quot;,w
                , &quot;float&quot;,h)
    return {&quot;X&quot;:x,&quot;Y&quot;:y,&quot;W&quot;:w,&quot;H&quot;:h}
  }

  SetFont(Font=&quot;&quot;,Size=&quot;&quot;,fStyle=&quot;&quot;,NoWrap=&quot;&quot;,RenderingHint=&quot;&quot;)
  {
    Style := 0, Styles := &quot;Regular|Bold|Italic|BoldItalic|Underline|Strikeout&quot;
    Loop, Parse, Styles, |
    {
      if RegExMatch(fStyle, &quot;\b&quot; A_loopField)
      Style |= (A_LoopField != &quot;StrikeOut&quot;) ? (A_Index-1) : 8
    }

    if (!this.hFormat or (this.prevNoWrap!=NoWrap and NoWrap!=&quot;&quot;))
    {
      if this.hFormat
      {
        DllCall(Graph.hGdipDeleteStringFormat,&quot;UPtr&quot;,this.hFormat)
        this.hFormat:=&quot;&quot;
      }
      DllCall(Graph.hGdipCreateStringFormat,&quot;int&quot;,NoWrap ? 0x4000 | 0x1000 : 0x4000,&quot;int&quot;,0,&quot;UPtr*&quot;,hFormat)
      this.hFormat:=hFormat
      this.prevNoWrap:=NoWrap
    }

    if (!this.hFamily or (this.prevFont!=Font and Font!=&quot;&quot;))
    {
      if this.hFamily
      {
        if this.hFont
        {
          DllCall(Graph.hGdipDeleteFont,&quot;UPtr&quot;,this.hFont)
          this.hFont:=&quot;&quot;
        }
        DllCall(Graph.hGdipDeleteFontFamily,&quot;UPtr&quot;,this.hFamily)
        this.hFamily:=&quot;&quot;
      }
      Font:=Font=&quot;&quot; ? &quot;Arial&quot; : Font
      if !A_IsUnicode
      {
        nSize:=DllCall(&quot;MultiByteToWideChar&quot;,&quot;uint&quot;,0,&quot;uint&quot;,0,&quot;Ptr&quot;,&amp;Font,&quot;int&quot;,-1,&quot;uint&quot;,0,&quot;int&quot;,0)
        VarSetCapacity(wFont,nSize*2)
        DllCall(&quot;MultiByteToWideChar&quot;,&quot;uint&quot;,0,&quot;uint&quot;,0,&quot;Ptr&quot;,&amp;Font,&quot;int&quot;,-1,&quot;Ptr&quot;,&amp;wFont,&quot;int&quot;,nSize)
      }
      DllCall(Graph.hGdipCreateFontFamilyFromName
              ,&quot;Ptr&quot;,A_IsUnicode ? &amp;Font : &amp;wFont
              ,&quot;uint&quot;,0
              ,&quot;UPtr*&quot;,hFamily)
      this.hFamily:=hFamily
      this.prevFont:=Font
    }

    if (!this.hFont or (this.prevSize!=Size and Size!=&quot;&quot;) or (this.prevfStyle!=fStyle and fStyle!=&quot;&quot;))
    {
      if this.hFont
      {
        DllCall(Graph.hGdipDeleteFont,&quot;UPtr&quot;,this.hFont)
        this.hFont:=&quot;&quot;
      }
      DllCall(Graph.hGdipCreateFont,&quot;UPtr&quot;,this.hFamily,&quot;float&quot;,(Size ? Size : 12),&quot;int&quot;,fStyle=&quot;&quot; ? 0 : Style,&quot;int&quot;,0,&quot;UPtr*&quot;,hFont)
      this.hFont:=hFont
      this.prevSize:=Size
      this.prevfStyle:=fStyle
    }

    RenderingHint:=(RenderingHint=&quot;&quot;) ? ((this.prevRenderingHint=&quot;&quot;) ? 0 : this.prevRenderingHint) : RenderingHint
    this.prevRenderingHint:=RenderingHint
    ; SystemDefault = 0
    ; SingleBitPerPixelGridFit = 1
    ; SingleBitPerPixel = 2
    ; AntiAliasGridFit = 3
    ; AntiAlias = 4
    DllCall(Graph.hGdipSetTextRenderingHint,&quot;UPtr&quot;,this.pGraphics,&quot;int&quot;,RenderingHint)
  }

  DrawString(Text,xpos=0,ypos=0,Width=0,Height=0,Options=&quot;Center&quot;,Colour=0xff000000,BackgroundColour=0)
  {
    RegExMatch(Options,&quot;i)Top|Up|Bottom|Down|vCentre|vCenter&quot;,vPos)

    if !(this.hFamily and this.hFont and this.hFormat)
      this.SetFont()

    Align := 0, Alignments := &quot;Near|Left|Centre|Center|Far|Right&quot;
    Loop, Parse, Alignments, |
    {
      if RegExMatch(Options, &quot;\b&quot; A_loopField)
        Align |= A_Index//2.1      ; 0|0|1|1|2|2
    }
    DllCall(Graph.hGdipSetStringFormatAlign,&quot;UPtr&quot;,this.hFormat,&quot;int&quot;,Align)

    if (!this.FontpBrush or Colour!=this.prevColour)
    {
      if this.FontpBrush
        Graph.DeleteBrush(this.FontpBrush)
      this.FontpBrush:=Graph.CreateSolidBrush(Colour)
      this.prevColour:=Colour
    }

    Graph.CreateRectF(RC,xpos,ypos,Width,Height)
    ReturnRC:=this.MeasureString(Text,RC)
    if (!Width or !Height)
    {
      Width:=Width ? Width : ReturnRC.W
      Height:=Height ? Height : ReturnRC.H
      Graph.CreateRectF(RC,xpos,ypos,Width,Height)
      ReturnRC:=this.MeasureString(Text,RC)
    }
    if BackgroundColour
      this.FillRectangle(BackgroundColour,xpos,ypos,Width,Height)
    if vPos
    {
      if (vPos=&quot;vCentre&quot; or vPos=&quot;vCenter&quot;)
        ypos+=(Height-ReturnRC.H)//2
      else if (vPos=&quot;Top&quot; or vPos=&quot;Up&quot;)
        ypos:=ReturnRC.Y
      else if (vPos=&quot;Bottom&quot; or vPos=&quot;Down&quot;)
        ypos+=Height-ReturnRC.H

      Graph.CreateRectF(RC,xpos,ypos,Width,ReturnRC.H)
      ReturnRC:=this.MeasureString(Text,RC)
    }

    sString:=Text
    if (!A_IsUnicode)
    {
      nSize:=DllCall(&quot;MultiByteToWideChar&quot;,&quot;uint&quot;,0,&quot;uint&quot;,0,&quot;Ptr&quot;,&amp;sString,&quot;int&quot;,-1,&quot;Ptr&quot;,0,&quot;int&quot;,0)
      VarSetCapacity(wString,nSize*2)
      DllCall(&quot;MultiByteToWideChar&quot;,&quot;uint&quot;,0,&quot;uint&quot;,0,&quot;Ptr&quot;,&amp;sString,&quot;int&quot;,-1,&quot;Ptr&quot;,&amp;wString,&quot;int&quot;,nSize)
    }
    DllCall(Graph.hGdipDrawString
            ,&quot;Ptr&quot;,this.pGraphics
            ,&quot;Ptr&quot;,A_IsUnicode ? &amp;sString : &amp;wString
            ,&quot;int&quot;,-1
            ,&quot;Ptr&quot;,this.hFont
            ,&quot;Ptr&quot;,&amp;RC
            ,&quot;Ptr&quot;,this.hFormat
            ,&quot;Ptr&quot;,this.FontpBrush)

    return ReturnRC
  }
  CreateRectF(ByRef RectF, x, y, w, h)
  {
    VarSetCapacity(RectF, 16)
    NumPut(x, RectF, 0, &quot;float&quot;), NumPut(y, RectF, 4, &quot;float&quot;), NumPut(w, RectF, 8, &quot;float&quot;), NumPut(h, RectF, 12, &quot;float&quot;)
  }

  MeasureString(sString,ByRef RectF)
  {
    VarSetCapacity(RC,16)
    if !A_IsUnicode
    {
      nSize:=DllCall(&quot;MultiByteToWideChar&quot;,&quot;uint&quot;,0,&quot;uint&quot;,0,&quot;Ptr&quot;,&amp;sString,&quot;int&quot;,-1,&quot;uint&quot;,0,&quot;int&quot;,0)
      VarSetCapacity(wString,nSize*2)
      DllCall(&quot;MultiByteToWideChar&quot;,&quot;uint&quot;,0,&quot;uint&quot;,0,&quot;Ptr&quot;,&amp;sString,&quot;int&quot;,-1,&quot;Ptr&quot;,&amp;wString,&quot;int&quot;,nSize)
    }

    if !DllCall(Graph.hGdipMeasureString
            ,&quot;Ptr&quot;,this.pGraphics
            ,&quot;Ptr&quot;,A_IsUnicode ? &amp;sString : &amp;wString
            ,&quot;int&quot;,-1
            ,&quot;Ptr&quot;,this.hFont
            ,&quot;Ptr&quot;,&amp;RectF
            ,&quot;Ptr&quot;,this.hFormat
            ,&quot;Ptr&quot;,&amp;RC
            ,&quot;uint*&quot;,Chars
            ,&quot;uint*&quot;,Lines)
    return {&quot;X&quot;:NumGet(RC, 0, &quot;float&quot;),&quot;Y&quot;:NumGet(RC, 4, &quot;float&quot;),&quot;W&quot;:NumGet(RC, 8, &quot;float&quot;),&quot;H&quot;:NumGet(RC, 12, &quot;float&quot;),&quot;Chars&quot;:Chars,&quot;Lines&quot;:Lines}
  }
}

#if WinActive(&quot;ahk_class AutoHotkeyGUI&quot;,Graph.WinText)
WheelUp::
MouseGetPos,,,,mC,2
Graph.WheelUp:=true
Graph.HotKeymC:=mC
return
WheelDown::
MouseGetPos,,,,mC,2
Graph.WheelDown:=true
Graph.HotKeymC:=mC
return
#if




/*  Windows Color Picker Plus (by rbrtryn)
    https-//autohotkey.com/board/topic/91229-windows-color-picker-plus/
    Edited by me lemasato-
        BGR2RGB()-
        Use Format() to convert the value to hex instead of SetFormat

    Function- ChooseColor([pRGB, hOwner, DlgX, DlgY, Palette])
        Displays a standard Windows dialog for choosing colors.
    Parameters-
        pRGB - The initial color to display in the dialog in RGB format.
               The default setting is Black.
        hOwner - The Window ID of the dialog&#039;s owner, if it has one. Defaults to
                0, i.e. no owner. If specified DlgX and DlgY are ignored.
        DlgX, DlgY - The X and Y coordinates of the upper left corner of the
                     dialog. Both default to 0.
        Palette - An array of up to 16 RGB color values. These become the
                  initial custom colors in the dialog.
    Remarks-
        The custom colors in the dialog are remembered between calls.

        If the user selects OK, the Palette array (if it exists) will be loaded
        with the custom colors from the dialog.
    Returns-
        If the user selects OK, the selected color is returned in RGB format
        and ErrorLevel is set to 0. Otherwise, the original pRGB value is
        returned and ErrorLevel is set to 1.
*/
ChooseColor(pRGB := 0, hOwner := 0, DlgX := 0, DlgY := 0, Palette*)
{
    static CustColors    ; Custom colors are remembered between calls
    static SizeOfCustColors := VarSetCapacity(CustColors, 64, 0)
    static StructSize := VarSetCapacity(ChooseColor, 9 * A_PtrSize, 0)

    CustData := (DlgX &lt;&lt; 16) | DlgY    ; Store X in high word, Y in the low word

;___Load user&#039;s custom colors
    for Index, Value in Palette
        NumPut(BGR2RGB(Value), CustColors, (Index - 1) * 4, &quot;UInt&quot;)

;___Set up a ChooseColor structure as described in the MSDN
    NumPut(StructSize, ChooseColor, 0, &quot;UInt&quot;)
    NumPut(hOwner, ChooseColor, A_PtrSize, &quot;UPtr&quot;)
    NumPut(BGR2RGB(pRGB), ChooseColor, 3 * A_PtrSize, &quot;UInt&quot;)
    NumPut(&amp;CustColors, ChooseColor, 4 * A_PtrSize, &quot;UPtr&quot;)
    NumPut(0x113, ChooseColor, 5 * A_PtrSize, &quot;UInt&quot;)
    NumPut(CustData, ChooseColor, 6 * A_PtrSize, &quot;UInt&quot;)
    NumPut(RegisterCallback(&quot;ColorWindowProc&quot;), ChooseColor, 7 * A_PtrSize, &quot;UPtr&quot;)

;___Call the function
    ErrorLevel := ! DllCall(&quot;comdlg32\ChooseColor&quot;, &quot;UPtr&quot;, &amp;ChooseColor, &quot;UInt&quot;)

;___Save the changes made to the custom colors
    if not ErrorLevel
        Loop 16
            Palette[A_Index] := BGR2RGB(NumGet(CustColors, (A_Index - 1) * 4, &quot;UInt&quot;))
    if !ErrorLevel ; modified by Alectric
      return BGR2RGB(NumGet(ChooseColor, 3 * A_PtrSize, &quot;UINT&quot;))
}

/*!
    Function- ColorWindowProc(hwnd, msg, wParam, lParam)
        Callback function used to modify the Color dialog before it is displayed
    Parameters-
        hwnd - Handle to the Color dialog window.
        msg - The message sent to the window.
        wParam - The handle to the control that has the keyboard focus.
        lParam - A pointer to the ChooseColor structure associated with the
                 Color dialog.
    Remarks-
        This is intended to be a private function, called only by ChooseColor.
        In response to a WM_INITDIALOG message, this function can be used to
        modify the Color dialog before it is displayed. Currently it just moves
        the window to a new X, Y location.
    Returns-
        If the hook procedure returns zero, the default dialog box procedure
        also processes the message. Otherwise, the default dialog box procedure
        ignores the message.
*/
ColorWindowProc(hwnd, msg, wParam, lParam)
{
    static WM_INITDIALOG := 0x0110

    if (msg &lt;&gt; WM_INITDIALOG)
        return 0

    hOwner := NumGet(lParam+0, A_PtrSize, &quot;UPtr&quot;)
    if (hOwner)
        return 0

    DetectSetting := A_DetectHiddenWindows
    DetectHiddenWindows On
    CustData := NumGet(lParam+0, 6 * A_PtrSize, &quot;UInt&quot;)
    DlgX := CustData &gt;&gt; 16, DlgY := CustData &amp; 0xFFFF
    WinMove ahk_id %hwnd%, , %DlgX%, %DlgY%

    DetectHiddenWindows %DetectSetting%
    return 0
}

/*!
    Function- BGR2RGB(Color)
        Converts a BGR color value to a RGB one or vice versa.
    Parameters-
        Color - The BGR or RGB value to convert
    Returns-
        The converted value.
*/
BGR2RGB(Color)
{
    ret :=  (Color &amp; 0xFF000000)
         | ((Color &amp; 0xFF0000) &gt;&gt; 16)
         |  (Color &amp; 0x00FF00)
         | ((Color &amp; 0x0000FF) &lt;&lt; 16)
    return ret
}
</code></pre></div>]]></summary>
			<author>
				<name><![CDATA[Alectric]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=26027</uri>
			</author>
			<updated>2023-05-10T15:15:35Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?id=17789&amp;action=new</id>
		</entry>
		<entry>
			<title type="html"><![CDATA[AHK: Авторизация в твиттере twitter]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?id=17783&amp;action=new" />
			<summary type="html"><![CDATA[<p>Пример кода получения твитов через поиск (нужна авторизация).<br />Для того, чтобы каждый раз не логиниться, после успешного логина стоит куда-нибудь сохранить csrf_token и auth_token и при следующем запуске скрипта их использовать.<br /></p><div class="codebox"><pre><code>username := &quot;username&quot;
email := &quot;email@gmail.com&quot;
password := &quot;password&quot;
authorization := &quot;Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA&quot;   ; twitter always uses exactly this one
auth_token = 0a0205c12365f6355613b6f2d486d474b6   ; if wrong, will be generated by script
csrf_token = b8caffd5c8d32770003427c21416baa0daff78c   ; if wrong, will be generated by script
WinHTTP := ComObjCreate(&quot;WinHTTP.WinHttpRequest.5.1&quot;)

getTweets:
url := &quot;https://twitter.com/i/api/2/search/adaptive.json?include_profile_interstitial_type=1&amp;include_blocking=1&amp;include_blocked_by=1&amp;include_followed_by=1&amp;include_want_retweets=1&amp;include_mute_edge=1&amp;include_can_dm=1&amp;include_can_media_tag=1&amp;include_ext_has_nft_avatar=1&amp;include_ext_is_blue_verified=1&amp;include_ext_verified_type=1&amp;include_ext_profile_image_shape=1&amp;skip_status=1&amp;cards_platform=Web-12&amp;include_cards=1&amp;include_ext_alt_text=true&amp;include_ext_limited_action_results=false&amp;include_quote_count=true&amp;include_reply_count=1&amp;tweet_mode=extended&amp;include_ext_views=true&amp;include_entities=true&amp;include_user_entities=true&amp;include_ext_media_color=true&amp;include_ext_media_availability=true&amp;include_ext_sensitive_media_warning=true&amp;include_ext_trusted_friends_metadata=true&amp;send_error_codes=true&amp;simple_quoted_tweet=true&amp;q=from%3AhtGOIW%20since%3A2009-11-01%20until%3A2011-01-01&amp;tweet_search_mode=live&amp;query_source=typed_query&amp;count=20&amp;requestContext=launch&amp;pc=1&amp;spelling_corrections=1&amp;include_ext_edit_control=true&amp;ext=mediaStats%2ChighlightedLabel%2ChasNftAvatar%2CvoiceInfo%2CbirdwatchPivot%2Cenrichments%2CsuperFollowMetadata%2CunmentionInfo%2CeditControl%2Cvibe&quot;
WinHTTP.Open(&quot;GET&quot;, url, true)
WinHTTP.SetRequestHeader(&quot;user-agent&quot;, &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36&quot;)
WinHTTP.SetRequestHeader(&quot;Pragma&quot;, &quot;no-cache&quot;)
WinHTTP.SetRequestHeader(&quot;Cache-Control&quot;, &quot;no-cache, no-store&quot;)
WinHTTP.SetRequestHeader(&quot;If-Modified-Since&quot;, &quot;Sat, 1 Jan 2000 00:00:00 GMT&quot;)
WinHTTP.SetRequestHeader(&quot;authorization&quot;, authorization)
WinHTTP.SetRequestHeader(&quot;x-csrf-token&quot;, csrf_token)
WinHTTP.SetRequestHeader(&quot;cookie&quot;, &quot;auth_token=&quot; auth_token &quot;; ct0=&quot; csrf_token)
WinHTTP.Send()
WinHTTP.WaitForResponse()
if RegexMatch(WinHTTP.ResponseText, &quot;^\{&quot;&quot;errors&quot;&quot;&quot;)
{
   GoSub login
   GoTo getTweets
}
msgbox % WinHTTP.ResponseText
return

login:
WinHTTP.Open(&quot;GET&quot;, &quot;https://twitter.com/i/flow/login&quot;, true)
WinHTTP.SetRequestHeader(&quot;user-agent&quot;, &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36&quot;)
WinHTTP.SetRequestHeader(&quot;Pragma&quot;, &quot;no-cache&quot;)
WinHTTP.SetRequestHeader(&quot;Cache-Control&quot;, &quot;no-cache, no-store&quot;)
WinHTTP.SetRequestHeader(&quot;If-Modified-Since&quot;, &quot;Sat, 1 Jan 2000 00:00:00 GMT&quot;)
WinHTTP.Send()
WinHTTP.WaitForResponse()
RegexMatch(WinHTTP.ResponseText, &quot;s)document\.cookie=&quot;&quot;gt=(.+?);&quot;, match)
guest_token := match1

body = {&quot;input_flow_data&quot;:{&quot;flow_context&quot;:{&quot;debug_overrides&quot;:{},&quot;start_location&quot;:{&quot;location&quot;:&quot;manual_link&quot;}}},&quot;subtask_versions&quot;:{&quot;action_list&quot;:2,&quot;alert_dialog&quot;:1,&quot;app_download_cta&quot;:1,&quot;check_logged_in_account&quot;:1,&quot;choice_selection&quot;:3,&quot;contacts_live_sync_permission_prompt&quot;:0,&quot;cta&quot;:7,&quot;email_verification&quot;:2,&quot;end_flow&quot;:1,&quot;enter_date&quot;:1,&quot;enter_email&quot;:2,&quot;enter_password&quot;:5,&quot;enter_phone&quot;:2,&quot;enter_recaptcha&quot;:1,&quot;enter_text&quot;:5,&quot;enter_username&quot;:2,&quot;generic_urt&quot;:3,&quot;in_app_notification&quot;:1,&quot;interest_picker&quot;:3,&quot;js_instrumentation&quot;:1,&quot;menu_dialog&quot;:1,&quot;notifications_permission_prompt&quot;:2,&quot;open_account&quot;:2,&quot;open_home_timeline&quot;:1,&quot;open_link&quot;:1,&quot;phone_verification&quot;:4,&quot;privacy_options&quot;:1,&quot;security_key&quot;:3,&quot;select_avatar&quot;:4,&quot;select_banner&quot;:2,&quot;settings_list&quot;:7,&quot;show_code&quot;:1,&quot;sign_up&quot;:2,&quot;sign_up_review&quot;:4,&quot;tweet_selection_urt&quot;:1,&quot;update_users&quot;:1,&quot;upload_media&quot;:1,&quot;user_recommendations_list&quot;:4,&quot;user_recommendations_urt&quot;:1,&quot;wait_spinner&quot;:3,&quot;web_modal&quot;:1}}
WinHTTP.Open(&quot;POST&quot;, &quot;https://api.twitter.com/1.1/onboarding/task.json?flow_name=login&quot;, true)
WinHTTP.SetRequestHeader(&quot;user-agent&quot;, &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36&quot;)
WinHTTP.SetRequestHeader(&quot;Pragma&quot;, &quot;no-cache&quot;)
WinHTTP.SetRequestHeader(&quot;Cache-Control&quot;, &quot;no-cache, no-store&quot;)
WinHTTP.SetRequestHeader(&quot;If-Modified-Since&quot;, &quot;Sat, 1 Jan 2000 00:00:00 GMT&quot;)
WinHTTP.SetRequestHeader(&quot;content-type&quot;, &quot;application/json&quot;)
WinHTTP.SetRequestHeader(&quot;authorization&quot;, authorization)
WinHTTP.SetRequestHeader(&quot;x-guest-token&quot;, guest_token)
WinHTTP.Send(body)
WinHTTP.WaitForResponse()
RegexMatch(WinHTTP.ResponseText, &quot;s)&quot;&quot;flow_token&quot;&quot;:&quot;&quot;(.+?)&quot;&quot;&quot;, match)
flow_token := match1

body = {&quot;flow_token&quot;:&quot;%flow_token%&quot;,&quot;subtask_inputs&quot;:[]}
WinHTTP.Open(&quot;POST&quot;, &quot;https://api.twitter.com/1.1/onboarding/task.json&quot;, true)
WinHTTP.SetRequestHeader(&quot;user-agent&quot;, &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36&quot;)
WinHTTP.SetRequestHeader(&quot;Pragma&quot;, &quot;no-cache&quot;)
WinHTTP.SetRequestHeader(&quot;Cache-Control&quot;, &quot;no-cache, no-store&quot;)
WinHTTP.SetRequestHeader(&quot;If-Modified-Since&quot;, &quot;Sat, 1 Jan 2000 00:00:00 GMT&quot;)
WinHTTP.SetRequestHeader(&quot;content-type&quot;, &quot;application/json&quot;)
WinHTTP.SetRequestHeader(&quot;authorization&quot;, authorization)
WinHTTP.SetRequestHeader(&quot;x-guest-token&quot;, guest_token)
WinHTTP.Send(body)
WinHTTP.WaitForResponse()
RegexMatch(WinHTTP.ResponseText, &quot;s)&quot;&quot;flow_token&quot;&quot;:&quot;&quot;(.+?)&quot;&quot;&quot;, match)
flow_token := match1

body = {&quot;flow_token&quot;:&quot;%flow_token%&quot;,&quot;subtask_inputs&quot;:[{&quot;subtask_id&quot;:&quot;LoginEnterUserIdentifierSSO&quot;,&quot;settings_list&quot;:{&quot;setting_responses&quot;:[{&quot;key&quot;:&quot;user_identifier&quot;,&quot;response_data&quot;:{&quot;text_data&quot;:{&quot;result&quot;:&quot;%username%&quot;}}}],&quot;link&quot;:&quot;next_link&quot;}}]}
WinHTTP.Open(&quot;POST&quot;, &quot;https://api.twitter.com/1.1/onboarding/task.json&quot;, true)
WinHTTP.SetRequestHeader(&quot;user-agent&quot;, &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36&quot;)
WinHTTP.SetRequestHeader(&quot;Pragma&quot;, &quot;no-cache&quot;)
WinHTTP.SetRequestHeader(&quot;Cache-Control&quot;, &quot;no-cache, no-store&quot;)
WinHTTP.SetRequestHeader(&quot;If-Modified-Since&quot;, &quot;Sat, 1 Jan 2000 00:00:00 GMT&quot;)
WinHTTP.SetRequestHeader(&quot;content-type&quot;, &quot;application/json&quot;)
WinHTTP.SetRequestHeader(&quot;authorization&quot;, authorization)
WinHTTP.SetRequestHeader(&quot;x-guest-token&quot;, guest_token)
WinHTTP.Send(body)
WinHTTP.WaitForResponse()
RegexMatch(WinHTTP.ResponseText, &quot;s)&quot;&quot;flow_token&quot;&quot;:&quot;&quot;(.+?)&quot;&quot;&quot;, match)
flow_token := match1

if InStr(WinHTTP.ResponseText, &quot;LoginEnterAlternateIdentifierSubtask&quot;)
{
   body = {&quot;flow_token&quot;:&quot;%flow_token%&quot;,&quot;subtask_inputs&quot;:[{&quot;subtask_id&quot;:&quot;LoginEnterAlternateIdentifierSubtask&quot;,&quot;enter_text&quot;:{&quot;text&quot;:&quot;%email%&quot;,&quot;link&quot;:&quot;next_link&quot;}}]}
   WinHTTP.Open(&quot;POST&quot;, &quot;https://api.twitter.com/1.1/onboarding/task.json&quot;, true)
   WinHTTP.SetRequestHeader(&quot;user-agent&quot;, &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36&quot;)
   WinHTTP.SetRequestHeader(&quot;Pragma&quot;, &quot;no-cache&quot;)
   WinHTTP.SetRequestHeader(&quot;Cache-Control&quot;, &quot;no-cache, no-store&quot;)
   WinHTTP.SetRequestHeader(&quot;If-Modified-Since&quot;, &quot;Sat, 1 Jan 2000 00:00:00 GMT&quot;)
   WinHTTP.SetRequestHeader(&quot;content-type&quot;, &quot;application/json&quot;)
   WinHTTP.SetRequestHeader(&quot;authorization&quot;, authorization)
   WinHTTP.SetRequestHeader(&quot;x-guest-token&quot;, guest_token)
   WinHTTP.Send(body)
   WinHTTP.WaitForResponse()
   RegexMatch(WinHTTP.ResponseText, &quot;s)&quot;&quot;flow_token&quot;&quot;:&quot;&quot;(.+?)&quot;&quot;&quot;, match)
   flow_token := match1
}

body = {&quot;flow_token&quot;:&quot;%flow_token%&quot;,&quot;subtask_inputs&quot;:[{&quot;subtask_id&quot;:&quot;LoginEnterPassword&quot;,&quot;enter_password&quot;:{&quot;password&quot;:&quot;%password%&quot;,&quot;link&quot;:&quot;next_link&quot;}}]}
WinHTTP.Open(&quot;POST&quot;, &quot;https://api.twitter.com/1.1/onboarding/task.json&quot;, true)
WinHTTP.SetRequestHeader(&quot;user-agent&quot;, &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36&quot;)
WinHTTP.SetRequestHeader(&quot;Pragma&quot;, &quot;no-cache&quot;)
WinHTTP.SetRequestHeader(&quot;Cache-Control&quot;, &quot;no-cache, no-store&quot;)
WinHTTP.SetRequestHeader(&quot;If-Modified-Since&quot;, &quot;Sat, 1 Jan 2000 00:00:00 GMT&quot;)
WinHTTP.SetRequestHeader(&quot;content-type&quot;, &quot;application/json&quot;)
WinHTTP.SetRequestHeader(&quot;authorization&quot;, authorization)
WinHTTP.SetRequestHeader(&quot;x-guest-token&quot;, guest_token)
WinHTTP.Send(body)
WinHTTP.WaitForResponse()
RegexMatch(WinHTTP.ResponseText, &quot;s)&quot;&quot;flow_token&quot;&quot;:&quot;&quot;(.+?)&quot;&quot;&quot;, match)
flow_token := match1

body = {&quot;flow_token&quot;:&quot;%flow_token%&quot;,&quot;subtask_inputs&quot;:[{&quot;subtask_id&quot;:&quot;AccountDuplicationCheck&quot;,&quot;check_logged_in_account&quot;:{&quot;link&quot;:&quot;AccountDuplicationCheck_false&quot;}}]}
WinHTTP.Open(&quot;POST&quot;, &quot;https://api.twitter.com/1.1/onboarding/task.json&quot;, true)
WinHTTP.SetRequestHeader(&quot;user-agent&quot;, &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36&quot;)
WinHTTP.SetRequestHeader(&quot;Pragma&quot;, &quot;no-cache&quot;)
WinHTTP.SetRequestHeader(&quot;Cache-Control&quot;, &quot;no-cache, no-store&quot;)
WinHTTP.SetRequestHeader(&quot;If-Modified-Since&quot;, &quot;Sat, 1 Jan 2000 00:00:00 GMT&quot;)
WinHTTP.SetRequestHeader(&quot;content-type&quot;, &quot;application/json&quot;)
WinHTTP.SetRequestHeader(&quot;authorization&quot;, authorization)
WinHTTP.SetRequestHeader(&quot;x-guest-token&quot;, guest_token)
WinHTTP.Send(body)
WinHTTP.WaitForResponse()
RegexMatch(WinHTTP.getallresponseheaders, &quot;s)Set-Cookie: ct0=(.+?);.+?Set-Cookie: auth_token=(.+?);&quot;, match)
csrf_token := match1, auth_token := match2

WinHTTP.Open(&quot;GET&quot;, &quot;https://twitter.com/i/api/graphql/XeaGEEWXp54Km4mVNvcMmg/Viewer?variables=%7B%22withCommunitiesMemberships%22%3Atrue%2C%22withSubscribedTab%22%3Atrue%2C%22withCommunitiesCreation%22%3Atrue%7D&amp;features=%7B%22blue_business_profile_image_shape_enabled%22%3Atrue%2C%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%7D&quot;, true)
WinHTTP.SetRequestHeader(&quot;user-agent&quot;, &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36&quot;)
WinHTTP.SetRequestHeader(&quot;Pragma&quot;, &quot;no-cache&quot;)
WinHTTP.SetRequestHeader(&quot;Cache-Control&quot;, &quot;no-cache, no-store&quot;)
WinHTTP.SetRequestHeader(&quot;If-Modified-Since&quot;, &quot;Sat, 1 Jan 2000 00:00:00 GMT&quot;)
WinHTTP.SetRequestHeader(&quot;authorization&quot;, authorization)
WinHTTP.SetRequestHeader(&quot;x-csrf-token&quot;, csrf_token)
WinHTTP.Send()
WinHTTP.WaitForResponse()
RegexMatch(WinHTTP.getallresponseheaders, &quot;s)Set-Cookie: ct0=(.+?);&quot;, match)
csrf_token := match1
msgbox % &quot;auth_token = &quot; auth_token &quot;`ncsrf_token = &quot; csrf_token
return</code></pre></div><p><a href="http://forum.script-coding.com/viewtopic.php?id=17784">Тема для обсуждения</a></p>]]></summary>
			<author>
				<name><![CDATA[Malcev]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=26930</uri>
			</author>
			<updated>2023-05-06T14:53:15Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?id=17783&amp;action=new</id>
		</entry>
		<entry>
			<title type="html"><![CDATA[AHK v2: Класс для работы с историей буфера обмена Windows 10]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?id=17722&amp;action=new" />
			<summary type="html"><![CDATA[<p>Частичная реализация класса <a href="https://learn.microsoft.com/en-us/uwp/api/windows.applicationmodel.datatransfer.clipboard?view=winrt-19041">Clipboard</a> из Windows <a href="https://learn.microsoft.com/en-us/uwp/api/">UWP</a> для AHK v2. Реализованы методы, нужные для работы с <a href="https://support.microsoft.com/en-us/windows/clipboard-in-windows-c436501e-985d-1c8d-97ea-fe46ddf338c6#WindowsVersion=Windows_10">историей буфера обмена</a> Windows 10. Доступные свойства и методы собраны в класс ClipboardHistory, по их названию должно быть понятно, что они делают. Остальные классы являются вспомогательными. Примеры использования в коде.<br /></p><div class="codebox"><pre><code>#Requires AutoHotkey v2.0.2 ; and OS &gt;= Windows 10

if !ClipboardHistory.HistoryEnabled {
    if MsgBox(&#039;Clipboard history disabled, want to enable?&#039;, &#039;Clipboard History&#039;, &#039;YN&#039;) = &#039;No&#039;
        ExitApp
    ClipboardHistory.HistoryEnabled := true
}
MsgBox &#039;Clipboard history enabled&#039;
MsgBox ClipboardHistory.GetHistoryItemText(1), &#039;First item text&#039;
MsgBox ClipboardHistory.DeleteHistoryItem(1), &#039;Boolean success status&#039;   ; deletes the first history item and returns boolean success status
MsgBox ClipboardHistory.GetHistoryItemText(1), &#039;First item text&#039;
MsgBox ClipboardHistory.PutHistoryItemIntoClipboard(2), &#039;Success status&#039; ; puts the second history item into Clipboard and returns succsess status
MsgBox A_Clipboard, &#039;Clipboard content&#039;

formats := &#039;&#039;
for fmt in ClipboardHistory.GetAvailableFormats(2) { ; available formats of the second history item
    formats .= fmt . &#039;`n&#039;
}
MsgBox formats, &#039;Available Formats&#039;

historyItemCount := ClipboardHistory.Count
Loop historyItemCount {
    MsgBox ClipboardHistory.GetHistoryItemText(A_Index), &#039;Item &#039; . A_Index . &#039; of &#039; . historyItemCount, &#039;T.7&#039;
}

class ClipboardHistory
{
; properties
    static HistoryEnabled {
        get =&gt; CBH_API.HistoryEnabled
        set =&gt; CBH_API.HistoryEnabled := value ; boolean
    }
    static Count =&gt; CBH_API.GetClipboardHistoryItems()

; methods
    static ClearHistory()                     =&gt; CBH_API.ClearHistory()
    static DeleteHistoryItem(index)           =&gt; CBH_API.DeleteHistoryItem(index) ; 1 based index
    static GetHistoryItemText(index)          =&gt; CBH_API.GetHistoryItemText(index)
    static PutHistoryItemIntoClipboard(index) =&gt; CBH_API.PutHistoryItemIntoClipboard(index)
    static GetAvailableFormats(index)         =&gt; CBH_API.GetAvailableFormats(index)
}

class CBH_API
{
    /*
        https://is.gd/bYyogJ     Clipboard Class (MSDN)
        https://is.gd/2z2Y9G     Windows.ApplicationModel.DataTransfer.0.h (GitHub)
        https://is.gd/T4Lb7b     asyncinfo.h (GitHub)
    */
    static HistoryEnabled {
        get =&gt; IClipboardStatics2.IsHistoryEnabled
        set =&gt; RegWrite(!!value, &#039;REG_DWORD&#039;, &#039;HKCU\SOFTWARE\Microsoft\Clipboard&#039;, &#039;EnableClipboardHistory&#039;)
    }

    static ClearHistory() =&gt; IClipboardStatics2.ClearHistory()

    static DeleteHistoryItem(index) { ; 1 based
        if !pIClipboardHistoryItem := this.GetClipboardHistoryItemByIndex(index)
            return false
        bool := IClipboardStatics2.DeleteItemFromHistory(pIClipboardHistoryItem)
        ObjRelease(pIClipboardHistoryItem)
        return bool
    }

    static GetHistoryItemText(index) {
        if !DataPackageView := this.GetDataPackageView(index)
            return
        ReadOnlyList := IReadOnlyList(DataPackageView.AvailableFormats)
        textFound := false
        Loop ReadOnlyList.Count
            HSTRING := ReadOnlyList.Item[A_Index - 1]
        until WrtString(HSTRING).GetText() = &#039;Text&#039; &amp;&amp; textFound := true
        if !textFound
            return
        HSTRING := this.Await(DataPackageView.GetTextAsync())
        return WrtString(HSTRING).GetText()
    }

    static GetAvailableFormats(index) {
        if !DataPackageView := this.GetDataPackageView(index)
            return
        ReadOnlyList := IReadOnlyList(DataPackageView.AvailableFormats)
        AvailableFormats := []
        Loop ReadOnlyList.Count {
            HSTRING := ReadOnlyList.Item[A_Index - 1]
            AvailableFormats.Push(WrtString(HSTRING).GetText())
        }
        return AvailableFormats
    }

    static GetDataPackageView(index) {
        if !pIClipboardHistoryItem := this.GetClipboardHistoryItemByIndex(index)
            return
        pIDataPackageView := IClipboardHistoryItem(pIClipboardHistoryItem).Content
        return IDataPackageView(pIDataPackageView)
    }

    static PutHistoryItemIntoClipboard(index) {
        static SetHistoryItemAsContentStatus := [&#039;Success&#039;, &#039;AccessDenied&#039;, &#039;ItemDeleted&#039;]
        if !pIClipboardHistoryItem := this.GetClipboardHistoryItemByIndex(index)
            return
        status := IClipboardStatics2.SetHistoryItemAsContent(pIClipboardHistoryItem)
        ObjRelease(pIClipboardHistoryItem)
        return SetHistoryItemAsContentStatus[status + 1]
    }

    static GetClipboardHistoryItemByIndex(index) { ; 1 based
        count := this.GetClipboardHistoryItems(&amp;ReadOnlyList)
        if (count &lt; index) {
            MsgBox &#039;Index &quot;&#039; . index . &#039;&quot; exceeds items count!&#039;
            return
        }
        return pIClipboardHistoryItem := ReadOnlyList.Item[index - 1]
    }

    static GetClipboardHistoryItems(&amp;ReadOnlyList := &#039;&#039;) {
        pIClipboardHistoryItemsResult := this.Await(IClipboardStatics2.GetHistoryItemsAsync())
        ClipboardHistoryItemsResult := IClipboardHistoryItemsResult(pIClipboardHistoryItemsResult)
        pIReadOnlyList := ClipboardHistoryItemsResult.Items
        ReadOnlyList := IReadOnlyList(pIReadOnlyList)
        return ReadOnlyList.Count
    }

    static Await(pIAsyncOperation) {
        static AsyncStatus := [&#039;Started&#039;, &#039;Completed&#039;, &#039;Canceled&#039;, &#039;Error&#039;]
        AsyncOperation := IAsyncOperation(pIAsyncOperation)
        pIAsyncInfo := AsyncOperation.QueryIAsyncInfo()
        AsyncInfo := IAsyncInfo(pIAsyncInfo)
        Loop {
            Sleep 10
            status := AsyncStatus[AsyncInfo.Status + 1]
        } until status != &#039;Started&#039;
        if (status != &#039;Completed&#039;)
            throw OSError(&#039;AsyncInfo error, status: &quot;&#039; . status . &#039;&quot;&#039;, A_ThisFunc)
        return AsyncOperation.GetResults()
    }
}

class IClipboardStatics2
{
    static __New() {
        static IID_IClipboardStatics2 := &#039;{D2AC1B6A-D29F-554B-B303-F0452345FE02}&#039;, VT_UNKNOWN := 0xD
        if !(A_OSVersion ~= &#039;^10\.&#039;)
            throw OSError(&#039;This class requires Windows 10 or later!&#039;, A_ThisFunc)
        
        WrtStr := WrtString(&#039;Windows.ApplicationModel.DataTransfer.Clipboard&#039;)
        DllCall(&#039;Ole32\CLSIDFromString&#039;, &#039;Str&#039;, IID_IClipboardStatics2, &#039;Ptr&#039;, CLSID := Buffer(16))
        this.comObj := ComValue(VT_UNKNOWN, WrtStr.GetFactory(CLSID))
        this.ptr := this.comObj.ptr
    }
    static GetHistoryItemsAsync() =&gt; (ComCall(6, this, &#039;UIntP&#039;, &amp;pIAsyncOperation := 0), pIAsyncOperation)

    static ClearHistory() =&gt; (ComCall(7, this, &#039;UIntP&#039;, &amp;res := 0), res)

    static DeleteItemFromHistory(pIClipboardHistoryItem) =&gt; (ComCall(8, this, &#039;Ptr&#039;, pIClipboardHistoryItem, &#039;UIntP&#039;, &amp;res := 0), res)

    static SetHistoryItemAsContent(pIClipboardHistoryItem) =&gt; (ComCall(9, this, &#039;Ptr&#039;, pIClipboardHistoryItem, &#039;UIntP&#039;, &amp;res := 0), res)

    static IsHistoryEnabled =&gt; (ComCall(10, this, &#039;UIntP&#039;, &amp;res := 0), res)
}

class IClipboardHistoryItemsResult extends _InterfaceBase
{
    Items =&gt; (ComCall(7, this, &#039;PtrP&#039;, &amp;pIReadOnlyList := 0), pIReadOnlyList)
}

class IClipboardHistoryItem extends _InterfaceBase
{
    Content =&gt; (ComCall(8, this, &#039;PtrP&#039;, &amp;pIDataPackageView := 0), pIDataPackageView)
}

class IDataPackageView extends _InterfaceBase
{
    AvailableFormats =&gt; (ComCall(9, this, &#039;PtrP&#039;, &amp;pIReadOnlyList := 0), pIReadOnlyList)

    GetTextAsync() =&gt; (ComCall(12, this, &#039;UIntP&#039;, &amp;pIAsyncOperation := 0), pIAsyncOperation)
}

class IReadOnlyList extends _InterfaceBase
{
    Item[index] =&gt; (ComCall(6, this, &#039;Int&#039;, index, &#039;PtrP&#039;, &amp;pItem := 0), pItem)

    Count =&gt; (ComCall(7, this, &#039;UIntP&#039;, &amp;count := 0), count)
}

class IAsyncOperation extends _InterfaceBase
{
    QueryIAsyncInfo() =&gt; ComObjQuery(this, IID_IAsyncInfo := &#039;{00000036-0000-0000-C000-000000000046}&#039;)

    GetResults() =&gt; (ComCall(8, this, &#039;PtrP&#039;, &amp;pResult := 0), pResult)
}

class IAsyncInfo extends _InterfaceBase
{
    Status =&gt; (ComCall(7, this, &#039;PtrP&#039;, &amp;status := 0), status)
}

class _InterfaceBase
{
    __New(ptr) {
        this.comObj := ComValue(VT_UNKNOWN := 0xD, ptr)
        this.ptr := this.comObj.ptr
    }
}

class WrtString
{
    __New(stringOrHandle) {
        if Type(stringOrHandle) = &#039;Integer&#039;
            this.ptr := stringOrHandle
        else {
            DllCall(&#039;Combase\WindowsCreateString&#039;, &#039;WStr&#039;, stringOrHandle, &#039;UInt&#039;, StrLen(stringOrHandle), &#039;PtrP&#039;, &amp;HSTRING := 0)
            this.ptr := HSTRING
        }
    }
    __Delete() {
        DllCall(&#039;Combase\WindowsDeleteString&#039;, &#039;Ptr&#039;, this)
    }
    GetText() {
        buf := DllCall(&#039;Combase\WindowsGetStringRawBuffer&#039;, &#039;Ptr&#039;, this, &#039;UIntP&#039;, &amp;len := 0, &#039;Ptr&#039;)
        return StrGet(buf, len, &#039;UTF-16&#039;)
    }
    GetFactory(riid) {
        hr := DllCall(&#039;Combase\RoGetActivationFactory&#039;, &#039;Ptr&#039;, this, &#039;Ptr&#039;, riid, &#039;PtrP&#039;, &amp;pInterface := 0)
        if (hr != 0)
            throw OSError(WrtString.SysError(hr), A_ThisFunc)
        return pInterface
    }
    static SysError(nError := &#039;&#039;) {
        static flags := (FORMAT_MESSAGE_ALLOCATE_BUFFER := 0x100) | (FORMAT_MESSAGE_FROM_SYSTEM := 0x1000)
        (nError = &#039;&#039; &amp;&amp; nError := A_LastError)
        DllCall(&#039;FormatMessage&#039;, &#039;UInt&#039;, flags, &#039;UInt&#039;, 0, &#039;UInt&#039;, nError, &#039;UInt&#039;, 0, &#039;PtrP&#039;, &amp;pBuf := 0, &#039;UInt&#039;, 128)
        err := (str := StrGet(pBuf)) = &#039;&#039; ? nError : str
        DllCall(&#039;LocalFree&#039;, &#039;Ptr&#039;, pBuf)
        return err
    }
}</code></pre></div><p>Связанная тема <a href="https://forum.script-coding.com/viewtopic.php?id=16028">AHK: UWP, Clipboard Class</a>, в ней выложен вариант для v1 (но с меньшей функциональностью).</p>]]></summary>
			<author>
				<name><![CDATA[teadrinker]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=24515</uri>
			</author>
			<updated>2023-04-02T22:27:13Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?id=17722&amp;action=new</id>
		</entry>
		<entry>
			<title type="html"><![CDATA[AHK: Захват монитора с помощью лупы Magnification API]]></title>
			<link rel="alternate" href="http://forum.script-coding.com/viewtopic.php?id=17699&amp;action=new" />
			<summary type="html"><![CDATA[<p><a href="https://learn.microsoft.com/en-us/previous-versions/windows/desktop/magapi/entry-magapi-sdk">https://learn.microsoft.com/en-us/previ … magapi-sdk</a><br />Алгоритм кода был сделан ранее <strong>serzh82saratov</strong> с небольшой моей помощью.<br />Я немного модифицировал его для сохранения рабочего стола в jpg.<br />Это может быть полезно для сохранения реального рабочего стола при использовании оверлеев с аппаратным ускорением, таких как виндовская лупа.<br /></p><div class="codebox"><pre><code>; global captureCursor := true   ; раскомментировать если нужен захват курсора.
; global hwndExclude := [0x1111, 0x2222]   ; массив hwnd окон, исключенных из захвата
OnExit, Uninitialize
setbatchlines -1
return

f11::
savedesktop(&quot;temp&quot;)
return

savedesktop(name)
{
   static hChildMagnifier, RECT, this
   varsetcapacity(this, StrLen(name)+1, 0)
   StrPut(name, &amp;this, &quot;utf-8&quot;)
   if !hChildMagnifier
   {
      Gui, Magnifier: +HWNDhMagnifier +toolwindow -Caption +E0x00080000 +E0x20 ; WS_EX_LAYERED := E0x00080000 WS_EX_TRANSPARENT := E0x20
      DllCall(&quot;LoadLibrary&quot;, &quot;str&quot;, &quot;magnification.dll&quot;)
      DllCall(&quot;magnification.dll\MagInitialize&quot;)
      if (captureCursor = &quot;&quot;)
         captureCursor := 0
      hChildMagnifier := DllCall(&quot;CreateWindowEx&quot;, &quot;uint&quot;, 0, &quot;str&quot;, &quot;Magnifier&quot;, &quot;str&quot;, &quot;MagnifierWindow&quot;, &quot;uint&quot;, (WS_CHILD := 0x40000000)|captureCursor, &quot;int&quot;, 0, &quot;int&quot;, 0, &quot;int&quot;, A_ScreenWidth, &quot;int&quot;, A_ScreenHeight, &quot;uptr&quot;, hMagnifier, &quot;uint&quot;, 0, &quot;ptr&quot;, DllCall(&quot;GetWindowLong&quot; (A_PtrSize=8 ? &quot;Ptr&quot; : &quot;&quot;), &quot;uptr&quot;, hMagnifier, &quot;int&quot;, GWL_HINSTANCE := -6 , &quot;ptr&quot;), &quot;uint&quot;, 0, &quot;ptr&quot;)
      if hwndExclude
      {
         VarSetCapacity(HWNDlist, hwndExclude.Count() * A_PtrSize, 0)
         loop % hwndExclude.Count()
            NumPut(hwndExclude[A_Index], HWNDlist, (A_Index - 1) * A_PtrSize, &quot;ptr&quot;)
         DllCall(&quot;magnification.dll\MagSetWindowFilterList&quot;, &quot;uptr&quot;, hChildMagnifier, &quot;int&quot;, MW_FILTERMODE_EXCLUDE := 0, &quot;int&quot;, hwndExclude.Count(), &quot;ptr&quot;, &amp;HWNDlist)
      }
      matrix := &quot;1|0|0|0|1|0|0|0|1&quot;  
      VarSetCapacity(MAGTRANSFORM, 36, 0)
      loop, parse, matrix, |
         NumPut(A_LoopField, MAGTRANSFORM, (A_Index-1)*4, &quot;float&quot;)
      DllCall(&quot;magnification.dll\MagSetWindowTransform&quot;, &quot;uptr&quot;, hChildMagnifier, &quot;ptr&quot;, &amp;MAGTRANSFORM)
      DllCall(&quot;magnification.dll\MagSetImageScalingCallback&quot;, &quot;uptr&quot;, hChildMagnifier, &quot;ptr&quot;, RegisterCallback(&quot;MagImageScalingCallback&quot;, &quot;Fast&quot;,, &amp;this))
      if (A_PtrSize = 8)
      {
         VarSetCapacity(RECT, 16, 0)
         NumPut(0, RECT, 0, &quot;int&quot;)
         NumPut(0, RECT, 4, &quot;int&quot;)
         NumPut(A_ScreenWidth, RECT, 8, &quot;int&quot;)
         NumPut(A_ScreenHeight, RECT, 12, &quot;int&quot;)
      }
   }
   if (A_PtrSize = 8)
      DllCall(&quot;magnification.dll\MagSetWindowSource&quot;, &quot;uptr&quot;, hChildMagnifier, &quot;ptr&quot;, &amp;RECT)
   else
      DllCall(&quot;magnification.dll\MagSetWindowSource&quot;, &quot;uptr&quot;, hChildMagnifier, &quot;int&quot;, 0, &quot;int&quot;, 0, &quot;int&quot;, A_ScreenWidth, &quot;int&quot;, A_ScreenHeight)
   return
}

MagImageScalingCallback(hwnd, srcdata, srcheader, destdata, destheader, unclipped, clipped, dirty)
{
   static hDC, BITMAPV5HEADER, BITMAPINFO, pCodec, extension := &quot;jpg&quot;
   if !hDC
   {  
      DllCall(&quot;LoadLibrary&quot;, &quot;str&quot;, &quot;gdiplus&quot;)
      VarSetCapacity(si, A_PtrSize = 8 ? 24 : 16, 0), si := Chr(1)
      DllCall(&quot;gdiplus\GdiplusStartup&quot;, &quot;ptr*&quot;, pToken, &quot;ptr&quot;, &amp;si, &quot;ptr&quot;, 0)
      DllCall(&quot;gdiplus\GdipGetImageEncodersSize&quot;, &quot;uint*&quot;, count:=0, &quot;uint*&quot;, size:=0)
      VarSetCapacity(ci, size)
      DllCall(&quot;gdiplus\GdipGetImageEncoders&quot;, &quot;uint&quot;, count, &quot;uint&quot;, size, &quot;ptr&quot;, &amp;ci)
      if !(count &amp;&amp; size)
         throw Exception(&quot;Could not get a list of image codec encoders on this system.&quot;)
      Loop % count
         EncoderExtensions := StrGet(NumGet(ci, (idx:=(48+7*A_PtrSize)*(A_Index-1))+32+3*A_PtrSize, &quot;uptr&quot;), &quot;UTF-16&quot;)
      until InStr(EncoderExtensions, &quot;*.&quot; extension)
      if !(pCodec := &amp;ci + idx)
         throw Exception(&quot;Could not find a matching encoder for the specified file format.&quot;)
      VarSetCapacity(BITMAPV5HEADER, 124, 0)
      NumPut(124, BITMAPV5HEADER, 0, &quot;uint&quot;)   ; bV5Size
      NumPut(A_ScreenWidth, BITMAPV5HEADER, 4, &quot;uint&quot;)   ; bV5Width
      NumPut(-A_ScreenHeight, BITMAPV5HEADER, 8, &quot;uint&quot;)   ; bV5Height
      NumPut(1, BITMAPV5HEADER, 12, &quot;short&quot;)   ; bV5Planes
      NumPut(32, BITMAPV5HEADER, 14, &quot;short&quot;)   ; bV5BitCount
      VarSetCapacity(BITMAPINFO, 44, 0)
      NumPut(44, BITMAPINFO, 0, &quot;uint&quot;)   ; biSize
      NumPut(A_ScreenWidth, BITMAPINFO, 4, &quot;uint&quot;)   ; biWidth
      NumPut(-A_ScreenHeight, BITMAPINFO, 8, &quot;UInt&quot;)   ; biHeight
      NumPut(1, BITMAPINFO, 12, &quot;short&quot;)   ; biPlanes
      NumPut(32, BITMAPINFO, 14, &quot;short&quot;)   ; biBitCount
      hDC := DllCall(&quot;GetDC&quot;, &quot;uptr&quot;, hwnd)
   }
   hBitmap := DllCall(&quot;CreateDIBitmap&quot;, &quot;ptr&quot;, hDC, &quot;ptr&quot;, &amp;BITMAPV5HEADER, &quot;uint&quot;, CBM_INIT := 6, &quot;ptr&quot;, srcdata, &quot;ptr&quot;, &amp;BITMAPINFO, &quot;uint&quot;, DIB_RGB_COLORS := 0, &quot;ptr&quot;)
   DllCall(&quot;gdiplus\GdipCreateBitmapFromHBITMAP&quot;, &quot;ptr&quot;, hBitmap, &quot;ptr&quot;, 0, &quot;ptr*&quot;, pBitmap)
   DllCall(&quot;gdiplus\GdipSaveImageToFile&quot;, &quot;ptr&quot;, pBitmap, &quot;wstr&quot;, strget(A_EventInfo, &quot;utf-8&quot;) &quot;.&quot; extension, &quot;ptr&quot;, pCodec, &quot;uint&quot;, 0)
   DllCall(&quot;gdiplus\GdipDisposeImage&quot;, &quot;ptr&quot;, pBitmap)
   DllCall(&quot;DeleteObject&quot;, &quot;ptr&quot;, hBitmap)
   return
}

Uninitialize: 	
DllCall(&quot;magnification.dll\MagUninitialize&quot;)
ExitApp</code></pre></div><p><a href="http://forum.script-coding.com/viewtopic.php?id=17700">Тема для обсуждения</a></p>]]></summary>
			<author>
				<name><![CDATA[Malcev]]></name>
				<uri>http://forum.script-coding.com/profile.php?id=26930</uri>
			</author>
			<updated>2023-03-18T14:39:19Z</updated>
			<id>http://forum.script-coding.com/viewtopic.php?id=17699&amp;action=new</id>
		</entry>
</feed>
