Beispiel #1
0
            protected override HandleRef BuildWindowCore(HandleRef hwndParent)
            {
                var wParent = (wnd)hwndParent.Handle;

                _w = WndUtil.CreateWindow(_wndProc = _WndProc, false, "Static", null, WS.CHILD | WS.CLIPCHILDREN, 0, 0, 0, 10, 10, wParent);

                return(new HandleRef(this, _w.Handle));
            }
Beispiel #2
0
    /// <summary>
    /// Called after loading workspace. Before executing startup scripts, adding tray icon and creating UI.
    /// </summary>
    public static void ProgramLoaded()
    {
        WndUtil.UacEnableMessages(Api.WM_COPYDATA, /*Api.WM_DROPFILES, 0x0049,*/ Api.WM_USER, Api.WM_CLOSE);
        //WM_COPYDATA, WM_DROPFILES and undocumented WM_COPYGLOBALDATA=0x0049 should enable drag/drop from lower UAC IL processes, but only through WM_DROPFILES/DragAcceptFiles, not OLE D&D.

        WndUtil.RegisterWindowClass(script.c_msgWndClassName, _WndProc);
        _msgWnd = WndUtil.CreateMessageOnlyWindow(script.c_msgWndClassName);
    }
Beispiel #3
0
        //Called from Main() in non-admin process when command line starts with /dd.
        public static void MainDD(string[] args)
        {
            _msgWnd = (wnd)args[1].ToInt();

            WndUtil.RegisterWindowClass("Au.Editor.DD", _WndProc);
            _w = WndUtil.CreateWindow("Au.Editor.DD", null, WS.POPUP | WS.DISABLED, WSE.LAYERED | WSE.NOACTIVATE | WSE.TOOLWINDOW | WSE.TOPMOST);
            Api.SetLayeredWindowAttributes(_w, 0, 1, 2);

            Thread.CurrentThread.TrySetApartmentState(ApartmentState.Unknown);             //uninit MTA
            Api.OleInitialize(default);
Beispiel #4
0
 public OpenClipboard_(bool createOwner, bool noOpenNow = false)
 {
     _isOpen = false;
     _w      = default;
     if (createOwner)
     {
         _w = WndUtil.CreateWindowDWP_(messageOnly: true);
         //MSDN says, SetClipboardData fails if OpenClipboard called with 0 hwnd. It doesn't, but better use hwnd.
     }
     if (!noOpenNow)
     {
         Reopen();
     }
 }
Beispiel #5
0
            /// <summary>
            /// Subclasses clipOwner.
            /// </summary>
            /// <param name="paste">true if used for paste, false if for copy.</param>
            /// <param name="data">If used for paste, can be string containing Unicode text or int/string dictionary containing clipboard format/data.</param>
            /// <param name="clipOwner">Our clipboard owner window.</param>
            /// <param name="wFocus">The target control or window.</param>
            public _ClipboardListener(bool paste, object data, wnd clipOwner, wnd wFocus)
            {
                _paste   = paste;
                _data    = data;
                _wndProc = _WndProc;
                _wFocus  = wFocus;
                WndUtil.SubclassUnsafe_(clipOwner, _wndProc);

                //rejected: use SetClipboardViewer to block clipboard managers/viewers/etc. This was used in QM2.
                //	Nowadays most such programs don't use SetClipboardViewer. They use AddClipboardFormatListener+WM_CLIPBOARDUPDATE.
                //	known apps that have clipboard viewer installed with SetClipboardViewer:
                //		OpenOffice, LibreOffice: tested Writer, Calc.
                //		VLC: after first Paste.
                //_wPrevClipViewer = Api.SetClipboardViewer(clipOwner);
                //print.it(_wPrevClipViewer);
            }
Beispiel #6
0
        //const string c_winClassName = "KTreeView";
        //static KTreeView() {
        //	WndUtil.RegisterWindowClass(c_winClassName);
        //}

        protected override HandleRef BuildWindowCore(HandleRef hwndParent)
        {
            var wParent = (wnd)hwndParent.Handle;

            //WndUtil.CreateWindow(_wndProc = _WndProc, false, c_winClassName, Name, WS.CHILD | WS.CLIPCHILDREN, 0, 0, 0, 10, 10, wParent);

            _w       = WndUtil.CreateWindow(_wndProc = _WndProc, false, "Static", Name, WS.CHILD | WS.CLIPCHILDREN, 0, 0, 0, 10, 10, wParent);
            _hasHwnd = true;
            _SetDpiAndItemSize(More.Dpi.OfWindow(_w));

            //Mouse messages must go to the parent window. On WM_NCHITTEST return HTTRANSPARENT.
            //	But UIA element from point does not work. Solution: let it be Static; UIA knows it.
            //Other tested ways don't work:
            //	WS.DISABLED. Scrollbars don't work.
            //	WSE.TRANSPARENT|WSE.LAYERED + Api.SetLayeredWindowAttributes(_w, 0, 0, 0). Unavailable on Win7. Scrollbars and UIA don't work.
            //	Relay mouse messages to the parent window. Does not work.

            return(new HandleRef(this, _w.Handle));
        }
Beispiel #7
0
            /// <returns>0 Cancel, 1 OK, 2 Retry.</returns>
            public int Show(Bitmap img, ICFlags flags, RECT r)
            {
                _img    = img;
                _flags  = flags;
                _cursor = MouseCursor.Load(ResourceUtil.GetBytes("<Au>resources/red_cross_cursor.cur"), 32);
                _dpi    = screen.primary.Dpi;
                _w      = WndUtil.CreateWindow(_WndProc, true, "#32770", "Au.uiimage.CaptureUI", WS.POPUP | WS.VISIBLE, WSE.TOOLWINDOW | WSE.TOPMOST, r.left, r.top, r.Width, r.Height);
                _w.ActivateL();

                try {
                    while (Api.GetMessage(out var m) > 0 && m.message != Api.WM_APP)
                    {
                        switch (m.message)
                        {
                        case Api.WM_KEYDOWN when !_capturing:
                            switch ((KKey)(int)m.wParam)
                            {
                            case KKey.Escape: return(0);

                            case KKey.F3: return(2);
                            }
                            break;

                        case Api.WM_RBUTTONUP when m.hwnd == _w:
                            switch (popupMenu.showSimple("1 Retry\tF3|2 Cancel\tEsc", owner: _w))
                            {
                            case 1: return(2);

                            case 2: return(0);
                            }
                            break;
                        }
                        Api.DispatchMessage(m);
                    }
                }
                finally {
                    var w = _w; _w = default;
                    Api.DestroyWindow(w);
                }
                return(_res);
            }
Beispiel #8
0
                //Currently supports only moving but not docking. Docking is implemented in _ContextMenu_Move+_MoveTo.
                public void Drag(POINT p)
                {
                    //bool canDock = false;
                    //_DockTarget target = null;
                    var   w    = this.Hwnd();
                    RECT  r    = w.Rect;
                    POINT offs = (p.x - r.left, p.y - r.top);
                    bool  ok   = WndUtil.DragLoop(w, MButtons.Left, d => {
                        if (d.msg.message != Api.WM_MOUSEMOVE)
                        {
                            return;
                        }

                        p = mouse.xy;
                        w.MoveL(p.x - offs.x, p.y - offs.y);

                        //if (!canDock && keys.gui.isAlt) {
                        //	canDock = true;
                        //	//w.SetTransparency(true, 128);
                        //	//_dockIndic = new _DockIndicator(_manager, this);
                        //	//_dockIndic.Show(this);
                        //}
                        ////if (canDock) _dockIndic.OnFloatMoved(_manager.PointToClient(p));
                    });

                    //if (canDock) {
                    //	w.SetTransparency(false);
                    //	_dockIndic.Close();
                    //	if (ok) {
                    //		target = _dockIndic.OnFloatDropped();
                    //	}
                    //	_dockIndic = null;
                    //}

                    //return target;
                }
Beispiel #9
0
            void _WmLbuttondown(POINT p0)
            {
                bool isColor = false;
                //bool isAnyShape = false; //rejected. Not useful.
                var ic = _flags & (ICFlags.Image | ICFlags.Color | ICFlags.Rectangle);

                if (ic == ICFlags.Color)
                {
                    isColor = true;
                }
                else
                {
                    var mod = keys.gui.getMod();
                    if (mod != 0 && ic == ICFlags.Rectangle)
                    {
                        return;
                    }
                    switch (mod)
                    {
                    case 0: break;

                    //case KMod.Shift: isAnyShape = true; break;
                    case KMod.Ctrl when ic == 0: isColor = true; break;

                    default: return;
                    }
                }

                Result = new ICResult();
                var r = new RECT(p0.x, p0.y, 0, 0);

                if (isColor)
                {
                    Result.color = (uint)_img.GetPixel(p0.x, p0.y).ToArgb();
                    r.right++; r.bottom++;
                }
                else
                {
                    //var a = isAnyShape ? new List<POINT>() { p0 } : null;
                    var  pen          = Pens.Red;
                    bool notFirstMove = false;
                    _capturing = true;
                    try {
                        if (!WndUtil.DragLoop(_w, MButtons.Left, m => {
                            if (m.msg.message != Api.WM_MOUSEMOVE)
                            {
                                return;
                            }
                            POINT p = m.msg.pt; _w.MapScreenToClient(ref p);
                            using var g = Graphics.FromHwnd(_w.Handle);
                            //if (isAnyShape) {
                            //	a.Add(p);
                            //	g.DrawLine(pen, p0, p);
                            //	p0 = p;
                            //} else {
                            if (notFirstMove)                               //erase prev rect
                            {
                                r.right++; r.bottom++;
                                g.DrawImage(_img, r, r, GraphicsUnit.Pixel);
                                //FUTURE: prevent flickering. Also don't draw under magnifier.
                            }
                            else
                            {
                                notFirstMove = true;
                            }
                            r = RECT.FromLTRB(p0.x, p0.y, p.x, p.y);
                            r.Normalize(true);
                            g.DrawRectangle(pen, r);
                            //}
                        }))                           //Esc key etc
                        {
                            Api.InvalidateRect(_w);
                            return;
                        }
                    }
                    finally { _capturing = false; }

                    //GraphicsPath path = null;
                    //if (isAnyShape && a.Count > 1) {
                    //	path = _CreatePath(a);
                    //	r = RECT.From(path.GetBounds(), false);
                    //} else {
                    r.right++; r.bottom++;
                    //}
                    if (r.NoArea)
                    {
                        Api.DestroyWindow(_w);
                        return;
                    }

                    if (ic != ICFlags.Rectangle)
                    {
                        var b = _img.Clone(r, PixelFormat.Format32bppArgb);
                        var d = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, b.PixelFormat);
                        try { unsafe { _SetAlpha((uint *)d.Scan0, r /*, path*/); } }
                        finally { b.UnlockBits(d); /*path?.Dispose();*/ }
                        Result.image = b;
                    }
                }
                _w.MapClientToScreen(ref r);
                Result.rect = r;

                _res = 1;
                Api.DestroyWindow(_w);
            }
Beispiel #10
0
        /// <summary>
        /// Runs/opens a program, document, directory (folder), URL, new email, Control Panel item etc.
        /// The returned <see cref="RResult"/> variable contains some process info - process id etc.
        /// </summary>
        /// <param name="file">
        /// Examples:
        /// - <c>@"C:\file.txt"</c>
        /// - <c>folders.Documents</c>
        /// - <c>folders.System + "notepad.exe"</c>
        /// - <c>@"%folders.System%\notepad.exe"</c>
        /// - <c>@"%TMP%\file.txt"</c>
        /// - <c>"notepad.exe"</c>
        /// - <c>@"..\folder\x.exe"</c>
        /// - <c>"http://a.b.c/d"</c>
        /// - <c>"file:///path"</c>
        /// - <c>"mailto:[email protected]"</c>
        /// - <c>":: ITEMIDLIST"</c>
        /// - <c>@"shell:::{CLSID}"</c>
        /// - <c>@"shell:AppsFolder\Microsoft.WindowsCalculator_8wekyb3d8bbwe!App"</c>.
        /// More info in Remarks.
        /// </param>
        /// <param name="args">
        /// Command line arguments.
        /// This function expands environment variables if starts with <c>"%"</c> or <c>"\"%"</c>.
        /// </param>
        /// <param name="flags"></param>
        /// <param name="dirEtc">
        /// Allows to specify more parameters: current directory, verb, etc.
        /// If string, it sets initial current directory for the new process. If "", gets it from <i>file</i>. More info: <see cref="ROptions.CurrentDirectory"/>.
        /// </param>
        /// <exception cref="ArgumentException">Used both <b>ROptions.Verb</b> and <b>RFlags.Admin</b> and this process isn't admin.</exception>
        /// <exception cref="AuException">Failed. For example, the file does not exist.</exception>
        /// <remarks>
        /// It works like when you double-click a file icon. It may start new process or not. For example it may just activate window if the program is already running.
        /// Uses API <msdn>ShellExecuteEx</msdn>.
        /// Similar to <see cref="Process.Start(string, string)"/>.
        ///
        /// The <i>file</i> parameter can be:
        /// - Full path of a file or directory. Examples: <c>@"C:\file.txt"</c>, <c>folders.Documents</c>, <c>folders.System + "notepad.exe"</c>, <c>@"%folders.System%\notepad.exe"</c>.
        /// - Filename of a file or directory, like <c>"notepad.exe"</c>. The function calls <see cref="filesystem.searchPath"/>.
        /// - Path relative to <see cref="folders.ThisApp"/>. Examples: <c>"x.exe"</c>, <c>@"subfolder\x.exe"</c>, <c>@".\subfolder\x.exe"</c>, <c>@"..\another folder\x.exe"</c>.
        /// - URL. Examples: <c>"https://www.example.com"</c>, <c>"file:///path"</c>.
        /// - Email, like <c>"mailto:[email protected]"</c>. Subject, body etc also can be specified, and Google knows how.
        /// - Shell object's ITEMIDLIST like <c>":: ITEMIDLIST"</c>. See <see cref="Pidl.ToHexString"/>, <see cref="folders.shell"/>. Can be used to open virtual folders and items like Control Panel.
        /// - Shell object's parsing name, like <c>@"shell:::{CLSID}"</c> or <c>@"::{CLSID}"</c>. See <see cref="Pidl.ToShellString"/>. Can be used to open virtual folders and items like Control Panel.
        /// - To run a Windows Store App, use <c>@"shell:AppsFolder\WinStoreAppId"</c> format. Examples: <c>@"shell:AppsFolder\Microsoft.WindowsCalculator_8wekyb3d8bbwe!App"</c>, <c>@"shell:AppsFolder\windows.immersivecontrolpanel_cw5n1h2txyewy!microsoft.windows.immersivecontrolpanel"</c>. To discover the string use hotkey Ctrl+Shift+Q or function <see cref="WndUtil.GetWindowsStoreAppId"/> or Google.
        ///
        /// Supports environment variables, like <c>@"%TMP%\file.txt"</c>. See <see cref="pathname.expand"/>.
        /// </remarks>
        /// <seealso cref="wnd.find"/>
        /// <seealso cref="wnd.findOrRun"/>
        /// <seealso cref="wnd.runAndFind"/>
        /// <example>
        /// Run Notepad and wait for an active Notepad window.
        /// <code><![CDATA[
        /// run.it("notepad.exe");
        /// 1.s();
        /// wnd w = wnd.wait(10, true, "*- Notepad", "Notepad");
        /// ]]></code>
        /// Run Notepad or activate a Notepad window.
        /// <code><![CDATA[
        /// wnd w = wnd.findOrRun("*- Notepad", run: () => run.it("notepad.exe"));
        /// ]]></code>
        /// Run File Explorer and wait for new folder window. Ignores matching windows that already existed.
        /// <code><![CDATA[
        /// var w = wnd.runAndFind(
        ///     () => run.it(@"explorer.exe"),
        ///     10, cn: "CabinetWClass");
        /// ]]></code>
        /// </example>
        public static RResult it(string file, string args = null, RFlags flags = 0, ROptions dirEtc = null)
        {
            Api.SHELLEXECUTEINFO x = default;
            x.cbSize = Api.SizeOf(x);
            x.fMask  = Api.SEE_MASK_NOZONECHECKS | Api.SEE_MASK_NOASYNC | Api.SEE_MASK_CONNECTNETDRV | Api.SEE_MASK_UNICODE;
            x.nShow  = Api.SW_SHOWNORMAL;

            bool curDirFromFile = false;
            var  more           = dirEtc;

            if (more != null)
            {
                x.lpVerb = more.Verb;
                if (x.lpVerb != null)
                {
                    x.fMask |= Api.SEE_MASK_INVOKEIDLIST;                                   //makes slower. But verbs are rarely used.
                }
                if (more.CurrentDirectory is string cd)
                {
                    if (cd.Length == 0)
                    {
                        curDirFromFile = true;
                    }
                    else
                    {
                        cd = pathname.expand(cd);
                    }
                    x.lpDirectory = cd;
                }

                if (!more.OwnerWindow.IsEmpty)
                {
                    x.hwnd = more.OwnerWindow.Hwnd.Window;
                }

                switch (more.WindowState)
                {
                case ProcessWindowStyle.Hidden: x.nShow = Api.SW_HIDE; break;

                case ProcessWindowStyle.Minimized: x.nShow = Api.SW_SHOWMINIMIZED; break;

                case ProcessWindowStyle.Maximized: x.nShow = Api.SW_SHOWMAXIMIZED; break;
                }

                x.fMask &= ~more.FlagsRemove;
                x.fMask |= more.FlagsAdd;
            }

            if (flags.Has(RFlags.Admin))
            {
                if (x.lpVerb == null || x.lpVerb.Eqi("runas"))
                {
                    x.lpVerb = "runas";
                }
                else if (!uacInfo.isAdmin)
                {
                    throw new ArgumentException("Cannot use Verb with flag Admin, unless this process is admin");
                }
            }

            file = _NormalizeFile(false, file, out bool isFullPath, out bool isShellPath);
            Pidl pidl = null;

            if (isShellPath)                  //":: ITEMIDLIST" or "::{CLSID}..." (we convert it too because the API does not support many)
            {
                pidl = Pidl.FromString(file); //does not throw
                if (pidl != null)
                {
                    x.lpIDList = pidl.UnsafePtr;
                    x.fMask   |= Api.SEE_MASK_INVOKEIDLIST;
                }
                else
                {
                    x.lpFile = file;
                }
            }
            else
            {
                x.lpFile = file;

                if (curDirFromFile && isFullPath)
                {
                    x.lpDirectory = pathname.getDirectory(file);
                }
            }
            x.lpDirectory ??= Directory.GetCurrentDirectory();
            if (!args.NE())
            {
                x.lpParameters = pathname.expand(args);
            }

            if (0 == (flags & RFlags.ShowErrorUI))
            {
                x.fMask |= Api.SEE_MASK_FLAG_NO_UI;
            }
            if (0 == (flags & RFlags.WaitForExit))
            {
                x.fMask |= Api.SEE_MASK_NO_CONSOLE;
            }
            if (0 != (flags & RFlags.MostUsed))
            {
                x.fMask |= Api.SEE_MASK_FLAG_LOG_USAGE;
            }
            x.fMask |= Api.SEE_MASK_NOCLOSEPROCESS;

            WndUtil.EnableActivate(-1);

            bool waitForExit = 0 != (flags & RFlags.WaitForExit);
            bool needHandle  = flags.Has(RFlags.NeedProcessHandle);

            bool ok = false; int pid = 0, errorCode = 0;
            bool asUser = !flags.HasAny(RFlags.Admin | RFlags.InheritAdmin) && uacInfo.isAdmin;             //info: new process does not inherit uiAccess

            if (asUser)
            {
                ok = Cpp.Cpp_ShellExec(x, out pid, out int injectError, out int execError);
                if (!ok)
                {
                    if (injectError != 0)
                    {
                        print.warning("Failed to run as non-admin.");
                        asUser = false;
                    }
                    else
                    {
                        errorCode = execError;
                    }
                }
            }
            if (!asUser)
            {
                ok = Api.ShellExecuteEx(ref x);
                if (!ok)
                {
                    errorCode = lastError.code;
                }
            }
            pidl?.Dispose();
            if (!ok)
            {
                throw new AuException(errorCode, $"*run '{file}'");
            }

            var         R  = new RResult();
            WaitHandle_ ph = null;

            if (needHandle || waitForExit)
            {
                if (pid != 0)
                {
                    x.hProcess = Handle_.OpenProcess(pid, Api.PROCESS_ALL_ACCESS);
                }
                if (!x.hProcess.Is0)
                {
                    ph = new WaitHandle_(x.hProcess, true);
                }
            }

            if (!waitForExit)
            {
                if (pid != 0)
                {
                    R.ProcessId = pid;
                }
                else if (!x.hProcess.Is0)
                {
                    R.ProcessId = process.processIdFromHandle(x.hProcess);
                }
            }

            try {
                Api.AllowSetForegroundWindow();

                if (x.lpVerb != null && Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
                {
                    Thread.CurrentThread.Join(50);                     //need min 5-10 for file Properties. And not Sleep.
                }
                if (ph != null)
                {
                    if (waitForExit)
                    {
                        ph.WaitOne();
                        if (Api.GetExitCodeProcess(x.hProcess, out var exitCode))
                        {
                            R.ProcessExitCode = exitCode;
                        }
                    }
                    if (needHandle)
                    {
                        R.ProcessHandle = ph;
                    }
                }
            }
            finally {
                if (R.ProcessHandle == null)
                {
                    if (ph != null)
                    {
                        ph.Dispose();
                    }
                    else
                    {
                        x.hProcess.Dispose();
                    }
                }
            }

            return(R);

            //tested: works well in MTA thread.
            //rejected: in QM2, run also has a 'window' parameter. However it just makes limited, unclear etc, and therefore rarely used. Instead use wnd.findOrRun or Find/Run/Wait like in the examples.
            //rejected: in QM2, run also has 'autodelay'. Better don't add such hidden things. Let the script decide what to do.
        }
Beispiel #11
0
        //rejected. Various problems, eg the program file cannot be moved. Unclear documentation.
        //	Guid _guid;

        static trayIcon()
        {
            s_msgTaskbarCreated = WndUtil.RegisterMessage("TaskbarCreated", uacEnable: true);
            WndUtil.RegisterWindowClass("trayIcon");
        }
Beispiel #12
0
        /// <summary>
        /// Makes triggers alive.
        /// </summary>
        /// <remarks>
        /// This function monitors hotkeys, activated windows and other events. When an event matches an added trigger, launches the thrigger's action, which runs in other thread by default.
        /// Does not return immediately. Runs until this process is terminated or <see cref="Stop"/> called.
        /// </remarks>
        /// <example>See <see cref="ActionTriggers"/>.</example>
        /// <exception cref="InvalidOperationException">Already running.</exception>
        /// <exception cref="AuException">Something failed.</exception>
        public unsafe void Run()
        {
            //Debug_.PrintLoadedAssemblies(true, true, true);

            ThrowIfRunning_();

            //bool haveTriggers = false;
            HooksThread.UsedEvents hookEvents = 0;
            _windowTriggers = null;
            for (int i = 0; i < _t.Length; i++)
            {
                var t = _t[i];
                if (t == null || !t.HasTriggers)
                {
                    continue;
                }
                //haveTriggers = true;
                switch ((TriggerType)i)
                {
                case TriggerType.Hotkey: hookEvents |= HooksThread.UsedEvents.Keyboard; break;

                case TriggerType.Autotext: hookEvents |= HooksThread.UsedEvents.Keyboard | HooksThread.UsedEvents.Mouse; break;

                case TriggerType.Mouse: hookEvents |= (t as MouseTriggers).UsedHookEvents_; break;

                case TriggerType.Window: _windowTriggers = t as WindowTriggers; break;
                }
            }
            //print.it(haveTriggers, (uint)llHooks);
            //if(!haveTriggers) return; //no. The message loop may be used for toolbars etc.

            if (!s_wasRun)
            {
                s_wasRun = true;
                WndUtil.RegisterWindowClass(c_cn);
            }
            _wMsg             = WndUtil.CreateMessageOnlyWindow(_WndProc, c_cn);
            _mainThreadId     = Api.GetCurrentThreadId();
            _winTimerPeriod   = 0;
            _winTimerLastTime = 0;

            if (hookEvents != 0)
            {
                //prevent big delay later on first LL hook event while hook proc waits
                if (!s_wasKM)
                {
                    s_wasKM = true;
                    ThreadPool.QueueUserWorkItem(_ => {
                        try {
                            //using var p1 = perf.local();
                            new wndFinder("*a").IsMatch(wnd.getwnd.root);                     //if used window scopes etc
                            _ = WindowsHook.LowLevelHooksTimeout;                             //slow JIT of registry functions
                            Jit_.Compile(typeof(ActionTriggers), nameof(_WndProc), nameof(_KeyMouseEvent));
                            Jit_.Compile(typeof(TriggerHookContext), nameof(TriggerHookContext.InitContext), nameof(TriggerHookContext.PerfEnd), nameof(TriggerHookContext.PerfWarn));
                            Jit_.Compile(typeof(ActionTrigger), nameof(ActionTrigger.MatchScopeWindowAndFunc));
                            Jit_.Compile(typeof(HotkeyTriggers), nameof(HotkeyTriggers.HookProc));
                            AutotextTriggers.JitCompile();
                            MouseTriggers.JitCompile();
                        }
                        catch (Exception ex) { Debug_.Print(ex); }
                    });
                }

                _thc = new TriggerHookContext(this);

                _ht = new HooksThread(hookEvents, _wMsg);
            }

            try {
                _evStop = Api.CreateEvent(false);
                _StartStopAll(true);
                IntPtr h = _evStop;
                _Wait(&h, 1);
            }
            finally {
                if (hookEvents != 0)
                {
                    _ht.Dispose(); _ht = null;
                }
                Api.DestroyWindow(_wMsg); _wMsg = default;
                Stopping?.Invoke(this, EventArgs.Empty);
                _evStop.Dispose();
                _StartStopAll(false);
                _mainThreadId = 0;
                _threads?.Dispose(); _threads = null;
            }

            void _StartStopAll(bool start)
            {
                foreach (var t in _t)
                {
                    if (t?.HasTriggers ?? false)
                    {
                        t.StartStop(start);
                    }
                }
            }
        }