Страница 238 из 243

Добавлено: Ср сен 09, 2026 17:02
AkulaBig
Orion9 писал(а):набор
Понятно, что делалось. Ладно, сделаю сейчас колонки. Дольше говорю.
Orion9 писал(а):Кстати, концепция с заворачиванием объекта Plugin в отдельный поток тоже рабочая.
Я проверял все варианты для ET. Правда отдельным потоком не запускал. Но и через объект Plugin и через утильку wdx тестил. То-есть все предоставляемые Ауторан варианты. Везде принцип работы одинаков.

Добавлено спустя 1 час 59 минут:

Orion9
Оказывается действительно и MI работает в ТС32 и ТС64 по разному. А я совсем забыл про это. Тогда вообще беда.
Я ваши намеки про сансея своеообразно понял. Может в лс чиркнете, что на самом деле происходит?

Добавлено: Вт сен 15, 2026 19:49
AkulaBig
Orion9
Можно в системное контекстное меню добавить свои пункты? Вернее в системное меню ТС. Кажется это не одно и то-же, что системное меню Винды.

Добавлено: Ср сен 16, 2026 12:40
Orion9
AkulaBig
В системное меню главного окна ТС была попытка добавить новые пункты, но они не работают. Ниже есть ответ самого Loopback почему.

В контекстное меню можно таким же образом попробовать добавить, но для начала придется с болваном изрядно пообщаться

[cut]Для динамического изменения стандартных или сторонних контекстных меню в Win32 API (C++) используется перехват оконных сообщений через хук вызовов процедур (CBT hook или GetMessage hook). Основная задача — поймать момент создания окна меню класса #32768 (системный класс для всплывающих меню), получить его хэндл (HMENU) и встроить туда свой элемент.
Ниже представлен классический способ установки хука WH_CBT перед вызовом TrackPopupMenu для внедрения кастомного пункта.
Реализация на C++ (Win32 API)
cpp
#include <windows.h>
#include <iostream>

// Глобальные переменные для управления хуком
HHOOK g_hHook = NULL;
UINT_PTR g_MyCustomItemID = 9999; // Уникальный ID вашего пункта меню

// Процедура хука CBT (Computer Based Training)
LRESULT CALLBACK CbtMenuHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
// HCBT_CREATEWND вызывается в момент создания любого окна
if (nCode == HCBT_CREATEWND)
HWND hWndMenu = (HWND)wParam;
wchar_t className[32];
GetClassNameW(hWndMenu, className, 32);

// #32768 — это предопределенный атомарный класс для Windows Context Menu
if (wcscmp(className, L"#32768") == 0)
{
// Посылаем сообщение меню, чтобы получить его HMENU хэндл
// (или используем MN_GETHMENU = 0x01E5 начиная с Windows 2000)
HMENU hMenu = (HMENU)SendMessageW(hWndMenu, 0x01E5, 0, 0);

if (hMenu)
{
// Вставляем разделитель
AppendMenuW(hMenu, MF_SEPARATOR, 0, NULL);
// Вставляем наш кастомный элемент в конец меню
AppendMenuW(hMenu, MF_STRING, g_MyCustomItemID, L"Мой кастомный пункт");
}
}
}
// Передаем управление следующим хукам в цепочке
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

// Пример функции вызова меню
void ShowMenuWithHook(HWND hWndOwner, HMENU hOriginalMenu, int x, int y)
{
// 1. Устанавливаем локальный WH_CBT хук на текущий поток
g_hHook = SetWindowsHookExW(WH_CBT, CbtMenuHookProc, NULL, GetCurrentThreadId());

// 2. Вызываем стандартный трекинг меню.
// Пока меню инициализируется, сработает наш хук CbtMenuHookProc.
TrackPopupMenu(hOriginalMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, hWndOwner, NULL);

// 3. Сразу после закрытия меню снимаем хук, чтобы не тратить ресурсы
if (g_hHook) {
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
}
Используйте код с осторожностью.
Как обработать нажатие?
Когда пользователь нажмет на добавленный пункт меню, операционная система отправит стандартное уведомление WM_COMMAND в окно-владелец (hWndOwner). Обработайте его в оконной процедуре (WndProc):
cpp
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
// Проверяем, совпадает ли ID с нашим кастомным пунктом
if (LOWORD(wParam) == g_MyCustomItemID)
{
MessageBoxW(hWnd, L"Кастомный пункт меню нажат!", L"Успех", MB_OK);
return 0;
}
break;
// ... обработка других сообщений ...
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
Используйте код с осторожностью.
⚠️ Важные нюансы
• Глобальный vs Локальный хук: Приведенный пример использует локальный хук (GetCurrentThreadId()), который модифицирует меню только внутри вашего приложения. Если ваша цель — внедриться в чужой процесс (например, в контекстное меню Проводника Windows/Explorer), вам потребуется компилировать хук в отдельную .dll и внедрять его глобально через WH_GETMESSAGE или использовать официальные Shell Extension (IContextMenu).
• Сообщение MN_GETHMENU (0x01E5): Это системное недокументированное сообщение, стабильно работающее во всех версиях Windows, начиная с Windows XP и заканчивая Windows 11. Оно возвращает внутренний хэндл HMENU для окна всплывающего меню.
[/cut]
[cut]Когда в системе или приложении одновременно открывается множество меню, идентифицировать именно ваше окно меню #32768 внутри хука можно по контекстным признакам. Само по себе окно меню обезличено, но у него есть связи с другими элементами интерфейса.
Вот 4 основных способа со 100% точностью определить, что перехваченное меню — то, которое вам нужно:
1. Проверка по координатам мыши (Самый простой способ)
Контекстное меню почти всегда открывается там, где пользователь кликнул мышкой. Вы можете сравнить позицию курсора с координатами создаваемого окна меню.
cpp
if (wcscmp(className, L"#32768") == 0)
{
// Получаем текущие координаты курсора
POINT ptCursor;
GetCursorPos(&ptCursor);

// Получаем координаты создаваемого меню
RECT rcMenu;
GetWindowRect(hWndTarget, &rcMenu);

// Проверяем, находится ли курсор в пределах или вблизи точки создания меню
// (делаем небольшой задел в 5-10 пикселей на случай смещения)
if (abs(rcMenu.left - ptCursor.x) < 10 && abs(rcMenu.top - ptCursor.y) < 10)
{
// Это с высокой вероятностью ваше меню, вызванное кликом!
}
}
Используйте код с осторожностью.
2. Проверка окна-владельца (Owner Window)
Каждое всплывающее меню привязано к конкретному окну (кнопке, форме или главному окну программы), которое его вызвало. Через функцию GetWindow с флагом GW_OWNER можно узнать хэндл (HWND) этого родительского окна и сравнить его с вашим.
cpp
if (wcscmp(className, L"#32768") == 0)
{
// Находим окно, которое создало/владеет этим меню
HWND hWndOwner = GetWindow(hWndTarget, GW_OWNER);

// g_MyTargetWindow — HWND вашего конкретного текстового поля, кнопки или формы
if (hWndOwner == g_MyTargetWindow)
{
// Меню принадлежит вашему конкретному элементу управления!
}
}
Используйте код с осторожностью.
3. Анализ существующего содержимого меню (Фильтрация по тексту пунктов)
Если вы знаете, какие пункты должны быть в целевом меню по умолчанию, вы можете проверить их наличие с помощью функции GetMenuStringW. Если меню содержит специфическое слово (например, "Копировать", "Свойства" или уникальное имя из сторонней библиотеки), значит это оно.
cpp
HMENU hMenu = (HMENU)SendMessageW(hWndTarget, 0x01E5, 0, 0);
if (hMenu)
{
wchar_t menuText[256];
// Проверяем, например, самый первый пункт меню (индекс 0)
GetMenuStringW(hMenu, 0, menuText, 256, MF_BYPOSITION);

// Если там написано то, что мы ожидаем увидеть в целевом меню:
if (wcscmp(menuText, L"Ожидаемый Пункт") == 0)
{
// Это наше меню. Встраиваем кастомный элемент.
}
}
Используйте код с осторожностью.
4. Включение хука строго «по требованию» (Паттерн Guard)
Вместо того чтобы держать хук постоянно активным и гадать, чье меню открылось, включайте его строго за миллисекунду до вызова меню и выключайте сразу же после.
Если меню вызывается сторонней функцией (ThirdPartyShowMenu()), оберните её:
cpp
// 1. Включаем хук прямо перед вызовом
g_hMenuHook = SetWindowsHookExW(WH_CBT, CbtMenuHookProc, NULL, GetCurrentThreadId());

// 2. Вызываем функцию, которая триггерит появление меню
ThirdPartyShowMenu();

// 3. Выключаем хук. Все остальные меню в системе теперь будут игнорироваться.
UnhookWindowsHookEx(g_hMenuHook);
Используйте код с осторожностью.
Примечание: Поскольку TrackPopupMenu внутри сторонних библиотек обычно блокирует поток до тех пор, пока меню не закроется, хук гарантированно перехватит только это конкретное меню и снимется сразу после его закрытия.
[/cut]
Это только для примера. На практике с ним можно долго общаться, пока он не перестанет выдавать что-то полезное.

Добавлено: Ср сен 16, 2026 16:12
AkulaBig
Orion9
Блин, обманул я вас. Системным я назвал контекстное меню Винды. То-есть контекстное меню ТС отличается от контекстного меню Винды. Надо было написать меню системы, то-есть Винды. Но все равно ваши примеры не напрасны. Когда-нибудь да пригодятся.
На данный момент я свою проблему решил. Надо было сделать смену программы для сравнивания перебором списка. То-есть не между внешней и внутренней. Трудность состояла в том, что сравнение используется в модальных окнах. Конкретно в Синхронизации. То-есть из окна ее не нажать. Можно запустить синхронизацию в отдельном потоке, но по моему мнению колхоз получается. То-есть свернуть окно Синхронизации, нажать на кнопку ПИ и вернуться обратно. Я забабахал через контекстное меню из Авторана. Первый раз такое делал. Работает по Ctrl+ЛКМ. Все отлично. Но кнопочку все равно придется сделать, так как надо как-то донести до пользователя, что переключать можно хоткеем. Вот за это я не люблю контекстное меню. Не информативно его использование. Конечно базу брал из вашей сборки :wink:

Добавлено: Ср сен 16, 2026 19:32
FallenAngel
У LoadLibrary есть аналог ahk-го #Include *i? Т.е. не выдавать ошибку not found - а молча пропускать, если не нашел?

Добавлено: Чт сен 17, 2026 06:15
AkulaBig
Orion9
А можно без перезагрузки отключить назначенное действие на кнопки мыши?
Вот я так сделал:

Код: Выделить всё

ControlSetMouseAction /L /K:C /K:S 0 "CompareToolShowMenu"
Как без перезагрузки ТС отключить это? В справке написано, что без имени функции действие не будет выполняться, но клавиши останутся заблокированы. Хочется чтобы хоткей очищался.

Добавлено: Чт сен 17, 2026 11:31
Orion9
AkulaBig
Мда... C болваном можно долго играться, идей у него масса.

Зарисовка на тему однажды Гислер проснулся и включил Total Commander :lol:

[cut]Изображение[/cut]
[cut="И так в каждом меню"]Тролль завёлся под меню? Может это просто глю..? :mrgreen:[/cut]

Код от болвана с небольшой правкой
[cut]

Код: Выделить всё

#Requires AutoHotkey v2.0
Persistent()

; 1. Путь к картинке (поддерживаются BMP, PNG, JPG)
IMAGE_PATH := "d:\Temp\Trollface.bmp" 

; 2. Создаем кастомное GUI для оверлея
overlay := Gui("+AlwaysOnTop -Caption +ToolWindow +E0x08000000") ; E0x08000000 = WS_EX_NOACTIVATE (не забирает фокус)
overlay.MarginX := 0
overlay.MarginY := 0
; Добавляем картинку с вашими размерами
picCtrl := overlay.Add("Picture", "w222 h180", IMAGE_PATH)
; Привязываем действие к клику по картинке
picCtrl.OnEvent("Click", MyImageClicked)

; 3. Константы хуков
EVENT_SYSTEM_MENUPOPUPSTART := 0x0006
EVENT_SYSTEM_MENUPOPUPEND   := 0x0007
WINEVENT_OUTOFCONTEXT       := 0x0000

; Ставим хуки на ОТКРЫТИЕ и ЗАКРЫТИЕ меню по всей системе
callback := CallbackCreate(WinEventProc, "Fast", 7)
hHookStart := DllCall("SetWinEventHook", "UInt", EVENT_SYSTEM_MENUPOPUPSTART, "UInt", EVENT_SYSTEM_MENUPOPUPSTART, "Ptr", 0, "Ptr", callback, "UInt", 0, "UInt", 0, "UInt", WINEVENT_OUTOFCONTEXT)
hHookEnd   := DllCall("SetWinEventHook", "UInt", EVENT_SYSTEM_MENUPOPUPEND, "UInt", EVENT_SYSTEM_MENUPOPUPEND, "Ptr", 0, "Ptr", callback, "UInt", 0, "UInt", 0, "UInt", WINEVENT_OUTOFCONTEXT)

WinEventProc(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime) {
    global overlay
    if hwnd == 0 
       overlay.Hide()
       
    if (WinGetClass(hwnd) == "#32768") {
        if (event == EVENT_SYSTEM_MENUPOPUPSTART) {
            Sleep(20) ; Ждем отрисовки оригинального меню
            
            ; Получаем координаты и размеры открывшегося чужого меню
            WinGetPos(&menuX, &menuY, &menuW, &menuH, hwnd)
            
            ; --- РАСЧЕТ ПОЗИЦИИ ---
            ; Покажем оверлей, например, строго ПОД чужим меню
            showX := menuX
            showY := menuY + menuH
            
            ; Или ВНУТРИ меню вместо первых пунктов (потребуется сдвиг)
            ; showX := menuX + 5
            ; showY := menuY + 5
            
            ; Отображаем наше GUI поверх чужого процесса
            overlay.Show("X" showX " Y" showY " NoActivate")
            
        } else if (event == EVENT_SYSTEM_MENUPOPUPEND) {
            ; Как только чужое меню закрылось — прячем оверлей
            overlay.Hide()
        }
    }
}

; 4. Функция, которая выполнится при клике на картинку поверх чужого меню
MyImageClicked(*) {
    overlay.Hide() ; Скрываем оверлей
    ;WinClose("#32768") ; Принудительно закрываем чужое контекстное меню
    
    MsgBox("Click")
}

; Снятие хуков при выходе
OnExit((*) => (
    DllCall("UnhookWinEvent", "Ptr", hHookStart),
    DllCall("UnhookWinEvent", "Ptr", hHookEnd),
    CallbackFree(callback)
))
[/cut]
В общем, есть там варианты, но их долго проверять. Болван хорош схемами, готовый код не всегда от него работает. Более менее рабочий вариант вот этот, но его нужно дорабатывать.

[cut]

Код: Выделить всё

#Requires AutoHotkey v2.0
Persistent()

; 1. Константы Win32 API
EVENT_SYSTEM_MENUPOPUPSTART := 0x0006
EVENT_SYSTEM_MENUPOPUPEND   := 0x0007
EVENT_OBJECT_FOCUS          := 0x8005 ; Событие изменения фокуса (наведения мыши)
WINEVENT_OUTOFCONTEXT       := 0x0000
MN_GETHMENU                 := 0x01E1

MY_CUSTOM_ID := 7777  

; Глобальные переменные для сохранения состояния
global lastFocusedID := 0
global currentMenuHwnd := 0
global currentMenuHandle := 0

; 2. Устанавливаем один общий хук на все необходимые фазы жизни меню
callback := CallbackCreate(WinEventProc, "Fast", 7)
hHookStart := DllCall("SetWinEventHook", "UInt", EVENT_SYSTEM_MENUPOPUPSTART, "UInt", EVENT_SYSTEM_MENUPOPUPSTART, "Ptr", 0, "Ptr", callback, "UInt", 0, "UInt", 0, "UInt", WINEVENT_OUTOFCONTEXT)
hHookEnd   := DllCall("SetWinEventHook", "UInt", EVENT_SYSTEM_MENUPOPUPEND,   "UInt", EVENT_SYSTEM_MENUPOPUPEND,   "Ptr", 0, "Ptr", callback, "UInt", 0, "UInt", 0, "UInt", WINEVENT_OUTOFCONTEXT)
hHookFocus := DllCall("SetWinEventHook", "UInt", EVENT_OBJECT_FOCUS,          "UInt", EVENT_OBJECT_FOCUS,          "Ptr", 0, "Ptr", callback, "UInt", 0, "UInt", 0, "UInt", WINEVENT_OUTOFCONTEXT)

WinEventProc(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime) {
    global currentMenuHwnd, currentMenuHandle, lastFocusedID, MY_CUSTOM_ID
    
    ; А) МЕНЮ ОТКРЫВАЕТСЯ
    if (event == EVENT_SYSTEM_MENUPOPUPSTART && WinGetClass(hwnd) == "#32768") {
        Sleep(15) 
        hMenu := DllCall("SendMessage", "Ptr", hwnd, "UInt", MN_GETHMENU, "Ptr", 0, "Ptr", 0, "Ptr")
        if (!hMenu) 
           return
        
        currentMenuHwnd := hwnd
        currentMenuHandle := hMenu
        lastFocusedID := 0 ; Сбрасываем старый фокус
        
        ; Внедряем наш кастомный пункт самым первым
        DllCall("InsertMenuW", "Ptr", hMenu, "UInt", 0, "UInt", 0x0400 | 0x0000, "Ptr", MY_CUSTOM_ID, "Str", "⚡ Независимый пункт")
        DllCall("DrawMenuBar", "Ptr", hwnd)
    }
    
    ; Б) ПОЛЬЗОВАТЕЛЬ НАВЕЛ МЫШКУ НА ПУНКТ (ФОКУСИРОВКА СТРОКИ)
    else if (event == EVENT_OBJECT_FOCUS && hwnd == currentMenuHwnd && currentMenuHandle != 0) {
        ; В событии EVENT_OBJECT_FOCUS параметр idChild содержит индекс (POSITION) элемента меню, на который навели мышь!
        if (idChild > 0) {
            menuIdx := idChild - 1 ; Переводим в zero-based индекс (0, 1, 2...)
            ; Получаем реальный ID элемента из хэндла меню
            focusedID := DllCall("GetMenuItemID", "Ptr", currentMenuHandle, "Int", menuIdx, "UInt")
            
            if (focusedID != 4294967295) { ; Игнорируем разделители
                lastFocusedID := focusedID ; Запоминаем текущий ID в памяти
            }
        }
    }
    
    ; В) МЕНЮ ЗАКРЫЛОСЬ (КЛИК СОВЕРШЕН)
    else if (event == EVENT_SYSTEM_MENUPOPUPEND && hwnd == currentMenuHwnd) {
        ; Если в момент закрытия меню последним сфокусированным элементом был наш пункт:
        if (lastFocusedID == MY_CUSTOM_ID) {
            ; Вызываем действие асинхронно через таймер, чтобы дать ОС корректно закрыть меню чужого процесса
            SetTimer(MyCustomAction, -10)
        }
        
        ; Полностью очищаем переменные
        currentMenuHwnd := 0
        currentMenuHandle := 0
        lastFocusedID := 0
    }
}

; Функция-обработчик успешного клика
MyCustomAction() {
    MsgBox("Пункт меню нажат! Перехвачено через фокус до закрытия меню.")
}

; Снятие хуков при закрытии скрипта
OnExit((*) => (
    DllCall("UnhookWinEvent", "Ptr", hHookStart),
    DllCall("UnhookWinEvent", "Ptr", hHookEnd),
    DllCall("UnhookWinEvent", "Ptr", hHookFocus),
    CallbackFree(callback)
))
[/cut]
Болван часто выводит код на С++, но AHK v2 легче всего переводить в Autorun. Для теста не нужно никаких сред разработок, достаточно одного exe файла и скрипта, это еще один плюс получения кода в AHK.
AkulaBig писал(а):Конечно базу брал из вашей сборки
Сборка это хорошо, но болван лучше! Ведь это был его код :lol:
AkulaBig писал(а): А можно без перезагрузки отключить назначенное действие на кнопки мыши?
Можно попробовать ключ /S, я думаю. Через него пропускать.
FallenAngel писал(а):У LoadLibrary есть аналог ahk-го #Include *i? Т.е. не выдавать ошибку not found - а молча пропускать, если не нашел?
Если нет других вариантов, то можно через DllCall

Код: Выделить всё

Static hLib = DllCall("LoadLibrary", "Wstr", sLib, "Ptr")
А перед этим просто проверить существование файла через FileExist

Добавлено: Чт сен 17, 2026 14:40
AkulaBig
Orion9 писал(а):Можно попробовать ключ /S, я думаю. Через него пропускать.
Вы про что? У ControlSetMouseAction нет такого ключа.
Через хуки идея конечно интересная. Попробую разобраться с кодом.

Добавлено: Пт сен 18, 2026 11:13
Orion9
AkulaBig писал(а):Вы про что?
Перепутал :lol:

Да, у ControlSetMouseAction такого ключа нет. Тогда скрипт пепрезапустить можно через RestartScript, если нужно обойтись без перезапуска ТС, но там есть свои нюансы. Или имитировать собственное нажатие через DllCall, если, конечно, оно будет работать.

Добавлено: Пт сен 18, 2026 15:42
AkulaBig
Orion9
Я нашел такую-же опцию у TCIMG. Правда там только через клавиши хоткей можно назначить. Там тоже требуется перезагрузка ТС для очистки хоткея.
Orion9 писал(а):огда скрипт пепрезапустить можно через RestartScript
Тоже интересная идея, надо попробовать.
Orion9 писал(а):Или имитировать собственное нажатие через DllCall, если, конечно, оно будет работать.
Все-же так глубоко не решусь копать. Хотя-бы из-за того, что не стоит опция, которой я вообще навряд ли буду пользоваться, да и мои пользователи тоже, таких затрат. У меня и так много нерешенных проблем весит. А опция вкл/выкл хоткея мне нужна лишь для кнопки на ПИ. А основное назначение кнопки, чтобы пользователь вообще узнал о возможности переключения программы сравнения. Нормальную я пирамиду построил?

Добавлено спустя 1 час 42 минуты:
Orion9 писал(а):RestartScript
Сработало! Спасибо за подсказку.
Но сразу возник вопрос. Хорошо, что в ТС32 проверил. Часто я забываю это делать. В ТС32 окно дергается при включении/отключении опции. Вот такой кусок кода:

Код: Выделить всё

If StatCompareToolMenuNum = 1 Then
	# Ctrl+Shift+ЛКМ
	# Ctrl+Shift+LMB
	ControlSetMouseAction /L /K:C /K:S 0 "CompareToolShowMenu"
Else
	# удаляем назначение Ctrl+Shift+ЛКМ
	# delete assignmentCtrl+Shift+LMB
	RestartScript(300)
Попробовал сделать:

Код: Выделить всё

If StatCompareToolMenuNum = 1 Then
	# Ctrl+Shift+ЛКМ
	# Ctrl+Shift+LMB
	ControlSetMouseAction /L /K:C /K:S 0 "CompareToolShowMenu"
Else
	# удаляем назначение Ctrl+Shift+ЛКМ
	# delete assignmentCtrl+Shift+LMB
	RestartScript(300)
Endif
WinRedraw(2)
Также дергается, а меню перестало появляться. Мы-же как-то устраняли дерганье окна? А при WinRedraw(1) падает.

Добавлено: Сб сен 19, 2026 08:44
AkulaBig
Orion9
Если в RestartScript() оставить время задержки по умолчанию, ТС не падает. Ну или реже падает. Но, конечно, следующая команда не выполняется, если достаточно быстро нажимать клавиши.

Добавлено: Сб сен 19, 2026 11:51
Orion9
AkulaBig
У меня сейчас нет возможности посмотреть и проверить, что там происходит, но RestartScript сырая была, вроде. Лучше делать эмуляцию, это интереснее и перспектив больше. Болванный юморист поможет. Жаль, что он код на Autorun сразу не выдает, но AHK2 сойдет.

Добавлено: Сб сен 19, 2026 12:19
AkulaBig
Orion9
Сделал на TCIMG. Там корректно все работает. Но когда использование меню включено, TCOMG.exe висит в памяти. Наверняка и у Авторана что-то висит, но хоть это не видно. В общем остановился все-же на Авторане. Конечно можно отследить в потоке рестарт скрипта, переменная для этого имеется и попытаться как-то это отработать. Но не буду с этим связываться. А через хуки просто несерьезно этим заниматься. Тем более я уже писал, что для меня ahk тоже самое, что для вас ру-борда.

Добавлено: Вс сен 20, 2026 17:15
AkulaBig
Orion9
Меня стали терзать смутные сомнения и я проверил все свои кнопки на ТС32. Блин, оказывается на ТС32 все команды по обновлению ПИ и подобные вызывают подергивание элементов :( Может попробуем решить эту проблему? У меня первая мысль проверить через другую команду.

Добавлено: Вт сен 22, 2026 12:10
Orion9
AkulaBig писал(а):А через хуки просто несерьезно этим заниматься.
Речь шла о переспективах на будущее. В теории на Autorun можно любой хук реализовать, или хотя бы попробовать: с низкоуровневым хуком WH_MOUSE_LL ведь получилось.

А пригодиться оно может где угодно, сделанный один раз код вряд ли не пропадет.
AkulaBig писал(а):Тем более я уже писал, что для меня ahk тоже самое, что для вас ру-борда.
Autorun и Autohotkey фактически одно и то же в плане синтаксиса и возможностей, нельзя этим не пользоваться. Болван неплохо выдает рабочий код на ahk, нужно лишь немного его перевести в формат aucfg.

Применительно к ControlSetMouseAction, о чем было выше. Если требуется пропустить клик и модификаторы. Для клика есть своя функция MouseClick(), с этим все просто. Для модификатора можно запросить код у болвана, чтобы не делать все с нуля. Например для CTRL

[cut]

Код: Выделить всё

#Requires AutoHotkey v2.0

; Горячие клавиши для теста:
F1::SendCtrlViaDll("down") ; Зажать Ctrl
^F2::SendCtrlViaDll("up")   ; Отпустить Ctrl

/**
 * Отправляет нажатие/отжатие клавиши Ctrl через прямой вызов SendInput API
 * @param {String} mode - "down" или "up"
 */
SendCtrlViaDll(mode := "down") {
    ; Определение констант Win32 API
    INPUT_KEYBOARD := 1
    VK_LCONTROL    := 0x11  ; Виртуальный код Левого Ctrl
    ID_LCONTROL    := 0x1D  ; Скан-код Левого Ctrl
    
    KEYEVENTF_EXTENDEDKEY := 0x0001
    KEYEVENTF_KEYUP       := 0x0002
    KEYEVENTF_SCANCODE    := 0x0008

    ; Определяем флаги на основе режима
    flags := KEYEVENTF_SCANCODE
    if (mode = "up") {
        flags |= KEYEVENTF_KEYUP
    }

    ; Создаем структуру INPUT (размер: 28 байт на 32-бит, 40 байт на 64-бит системах)
    ; Для универсальности выделяем 40 байт памяти
    rectInput := Buffer(A_PtrSize == 8 ? 40 : 28, 0)
    
    ; Заполняем структуру:
    NumPut("UInt", INPUT_KEYBOARD, rectInput, 0) ; type (0-4 байт)
    
    ; Смещение для вложенной структуры KEYBDINPUT начинается с 8 байта на х64
    wKeybdOffset := A_PtrSize == 8 ? 8 : 4
    
    NumPut("UShort", VK_LCONTROL, rectInput, wKeybdOffset)       ; wVk
    NumPut("UShort", ID_LCONTROL, rectInput, wKeybdOffset + 2)   ; wScan
    NumPut("UInt",   flags,       rectInput, wKeybdOffset + 4)   ; dwFlags
    NumPut("UInt",   0,           rectInput, wKeybdOffset + 8)   ; time
    NumPut("UPtr",   0,           rectInput, wKeybdOffset + 12)  ; dwExtraInfo

    ; Вызываем SendInput. 
    ; Первый параметр - количество структур (1), второй - указатель на буфер, третий - размер структуры.
    DllCall("User32.dll\SendInput", "UInt", 1, "Ptr", rectInput, "Int", rectInput.Size)
}
[/cut]
Вторая версия функции на ahk2

[cut]

Код: Выделить всё

; --- ФУНКЦИЯ ОТПРАВКИ ЧЕРЕЗ SENDINPUT ---
SendLCtrlViaDll(mode := "down") {
    INPUT_KEYBOARD := 1
    VK_LCONTROL    := 0x11
    KEYEVENTF_KEYUP := 0x0002

    ; Создаем структуру INPUT (40 байт для x64 / 28 байт для x86)
    rectInput := Buffer(A_PtrSize == 8 ? 40 : 28, 0)
    
    NumPut("UInt", INPUT_KEYBOARD, rectInput, 0) ; type
    
    wKeybdOffset := A_PtrSize == 8 ? 8 : 4
    NumPut("UShort", VK_LCONTROL, rectInput, wKeybdOffset) ; wVk
    NumPut("UShort", 0,           rectInput, wKeybdOffset + 2) ; wScan (0, работаем по VK)
    
    ; Нажатие (0) или Отжатие (KEYEVENTF_KEYUP)
    flags := (mode == "up") ? KEYEVENTF_KEYUP : 0
    NumPut("UInt", flags, rectInput, wKeybdOffset + 4) ; dwFlags

    DllCall("User32.dll\SendInput", "UInt", 1, "Ptr", rectInput, "Int", rectInput.Size)
}
[/cut]

Перевод в Autorun

[cut]

Код: Выделить всё

Func SendLCtrlViaDll(mode = "down")
    INPUT_KEYBOARD = 1
    KEYEVENTF_KEYUP = 0x0002

    # структура INPUT (40 байт для x64 / 28 байт для x86)
    static rectInput = Buffer(auPtrSize = 8 ? 40 : 28)
    rectInput.Zero()
    
    rectInput.SetNum(0, "UInt", INPUT_KEYBOARD) # type
    
    wKeybdOffset = auPtrSize = 8 ? 8 : 4
    rectInput.SetNum(wKeybdOffset, "UShort", 0x11) # wVk
    rectInput.SetNum(wKeybdOffset + 2, "UShort", 0) # wScan (0, работаем по VK)
    
    # Нажатие (0) или Отжатие (KEYEVENTF_KEYUP)
    flags = mode = "up" ? KEYEVENTF_KEYUP : 0
    rectInput.SetNum(wKeybdOffset + 4, "UInt", flags) # dwFlags
    
    DllCall("User32.dll\SendInput", "UInt", 1, "Ptr", rectInput.ptr, "Int", rectInput.Size)
EndFunc
[/cut]

Тест на ControlSetMouseAction /L /K:C 3 SetMediaInfo (есть в сборке)

[cut]

Код: Выделить всё

ControlSetMouseAction /L /K:C 3 SetMediaInfo

Func SetMediaInfo()
 
    SendLCtrlViaDll()
    Sleep(10)    
    MouseClick()
    Sleep(10)
    SendLCtrlViaDll("up") 
    Return
    
    MediaInfoNum += 1
    If MediaInfoNum > 4 Then MediaInfoNum = 1
    
    Switch MediaInfoNum
    Case 1
       MediaInfoParams = " --Language=file://ru.csv"
    Case 2
       MediaInfoParams = " --Complete=1 --Language=file://ru.csv"
    Case 3
       MediaInfoParams = " --Complete=0"
    Case 4
       MediaInfoParams = " --Output=file://hint.txt"       
    EndSwitch
    
    ShowHint("Параметры MediaInfo: " & MediaInfoParams, "", "", 1500)

EndFunc
[/cut]
Sleep нужен, иначе что-то там не успевает перевариваься и не совсем корректно работает. Код в начале пропускает Ctrl+Ciick, привязанный к ControlSetMouseAction, хотя это, конечно, костыль без гарантий, но можно попробовать и с другими модификаторами, если очень нужно.

Добавлено спустя 5 минут:
AkulaBig писал(а):Может попробуем решить эту проблему?
Вы бы гифку записали, а то не понятно о чем речь.