win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Здесь обсуждается всё, что касается макросов.
Post Reply
User avatar
John Doe
Бюрократ
Posts: 14580
Joined: Wed 27 Apr, 2005 20:42
Location: github.com/FarManagerLegacy
Has thanked: 92 times
Been thanked: 514 times
Contact:

win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Post by John Doe »

Когда-то понадобилось мне расширить возможности mf.waitkey, чтобы получать не просто наименование клавиши, а и полный InputRecord.
Нужно это было для того, чтобы даже с нажатым Alt иметь возможность определить нажатый символ, в правильным регистре, и с текущей раскладкой клавиатуры.

Тогда это не решилось, а сейчас дошли руки разобраться и решить эту задачу (и зафиксировать это тут в качестве ликбеза).

Итак, в апи у нас присутствуют помимо mf.waitkey такие функции:
  • ACTL_WAITKEY ничем не поможет, т.к. может только ожидать указанную клавишу.
    При этом умеет правильно обрабатывать не любой заданный InputRecord, а только то что различает FarInputRecordToName.
    Т.е. например ждать нажатия модификаторов типа Shift не получится (ACTL_WAITKEY сразу возвращается).
  • win.ExtractKey тоже не подходит - умеет возвращать только VK в строковом виде.
    Безразлична к сочетаниям клавиш, реагирует чётко на нажатия отдельных клавиш, и для модификаторов тоже.
    Этим она отличается от mf.waitkey, которая возвращает нажатия целых сочетаний (а для отдельно клацнутых модификаторов - только отпускания).
  • В редакторе editor.ReadInput даёт возможность читать голые InputRecords.
    Это почти то что нужно, но есть нюанс: если нажатая комбинация соответствует макросу, то функция её не получит.
  • Остаётся win.ExtractKeyEx и она делает именно то, чего мы от неё хотим.
Однако поскольку она (равно как editor.ReadInput и Process(Editor|Dialog|Console)Input) работает на низком уровне, а если же мы хотим получать не весь поток событий, а именно комбинации в том виде как они распознаются фаром (и используются в макросах), то этот поток легко фильтровать функцией far.InputRecordToName.

Т.е. условно функциональность waitkey можно повторить таким кодом:

Code: Select all

INTERVAL = 10
waitkey = (timeout=0) ->
  while timeout>0
    rec = win.ExtractKeyEx!
    if rec
      if name = far.InputRecordToName rec
        return name
    win.Sleep INTERVAL
    timeout -= INTERVAL

-- test
if name = waitkey(2000)
  far.Message name
else
  far.Message "nothing pressed"
https://t.me/FarManager — Telegram чат
User avatar
John Doe
Бюрократ
Posts: 14580
Joined: Wed 27 Apr, 2005 20:42
Location: github.com/FarManagerLegacy
Has thanked: 92 times
Been thanked: 514 times
Contact:

win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Post by John Doe »

На практике этого недостаточно, что можно увидеть на примере такого кода:
Если нажать сочетание с модификатором, например CtrlA, то мы получим сразу 2 события:

Code: Select all

CtrlA
Ctrl
Второе событие - это отпускание Ctrl

Чтобы отфильтровать "ложное" событие, требуется в нашей waitkey заменить вызов far.InputRecordToName на чуть более сложный фильтр:

Edit:
Сделаем максимально близко к mf.waitkey:

Code: Select all

F = far.Flags
local lastdown
processRec = (rec)->
  with rec
    if .EventType==F.KEY_EVENT
      name = far.InputRecordToName rec
      if name and not .KeyDown
        name = nil unless lastdown
      lastdown = not name and .KeyDown
      return name
  nil
После этого поведение нашей waitkey соответствует желаемому, и можно заниматься дополнительными усовершенствованиями.
https://t.me/FarManager — Telegram чат
User avatar
John Doe
Бюрократ
Posts: 14580
Joined: Wed 27 Apr, 2005 20:42
Location: github.com/FarManagerLegacy
Has thanked: 92 times
Been thanked: 514 times
Contact:

win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Post by John Doe »

Итак, цель в том, чтобы получать не только клавиши, но и символы.
Нюансы:
  • управляющие клавиши такие как BS, Tab, Esc, Enter тоже дают символы
  • сочетания с Ctrl тоже дают UnicodeChar, для букв это string.byte(char), а для пробела остаётся " ".
  • AltGr в Far обрабатывается исключительно при LCtrl и RAlt, в прочих же сочетаниях символы надо подавлять.
Учтём это:

Code: Select all

-- todo: should depend on kbd layout
ALTGR = F.LEFT_CTRL_PRESSED + F.RIGHT_ALT_PRESSED
MODS = ALTGR + F.RIGHT_CTRL_PRESSED + F.LEFT_ALT_PRESSED
INTERVAL = 10
waitkey = (timeout=0) ->
  while timeout>0
    rec = win.ExtractKeyEx!
    if rec
      if name = processRec rec
        with rec
          return name, do
            if .UnicodeChar and (.UnicodeChar=="\t" or .UnicodeChar>=" ")
              mods = bit64.band .ControlKeyState, MODS
              if mods==0 or .UnicodeChar~=" " and mods==ALTGR
                .UnicodeChar
    win.Sleep INTERVAL
    timeout -= INTERVAL

-- test
while true
  name,char = waitkey(2000)
  if char
    win.OutputDebugString(char)
  elseif not name
    win.OutputDebugString("timeout")
    break
Задача выполнена, waitkey теперь вторым значением возвращает символ.
https://t.me/FarManager — Telegram чат
User avatar
John Doe
Бюрократ
Posts: 14580
Joined: Wed 27 Apr, 2005 20:42
Location: github.com/FarManagerLegacy
Has thanked: 92 times
Been thanked: 514 times
Contact:

win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Post by John Doe »

Минимальными модификациями можно добиться других интересных возможностей.
Например, воспроизвести функциональность из меню фара, когда курсор "зацикливается" при одиночном нажатии стрелок, а при удержании - останавливается на границе.

Или функции MacroEx.
И т.п.
https://t.me/FarManager — Telegram чат
User avatar
John Doe
Бюрократ
Posts: 14580
Joined: Wed 27 Apr, 2005 20:42
Location: github.com/FarManagerLegacy
Has thanked: 92 times
Been thanked: 514 times
Contact:

win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Post by John Doe »

Бонус: утилита для отображения клавиатурных событий.
Запускать: moon:@readkey.moon.
Или через LuaShell.



В настоящий момент mf.printconsole добавляет лишние переводы строк, при запуске через LuaShell всё выглядит лучше.
https://t.me/FarManager — Telegram чат
User avatar
HaRT
Moderator
Posts: 11454
Joined: Tue 30 Aug, 2005 17:21
Has thanked: 270 times
Been thanked: 417 times

win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Post by HaRT »

Нет ли всего этого великолепия в каком-нибудь репозитории на GitHub или Chisel?
Фар есть инструмент, а не нянька. © 2009 DrKnS
User avatar
John Doe
Бюрократ
Posts: 14580
Joined: Wed 27 Apr, 2005 20:42
Location: github.com/FarManagerLegacy
Has thanked: 92 times
Been thanked: 514 times
Contact:

win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Post by John Doe »

readkey есть в репозитории LuaShell.
Остальное это куски кода, с которыми я игрался в процессе переписывания IncSearch, и как по мне то отдельно они тянут разве что на форумный пост.
https://t.me/FarManager — Telegram чат
User avatar
HaRT
Moderator
Posts: 11454
Joined: Tue 30 Aug, 2005 17:21
Has thanked: 270 times
Been thanked: 417 times

win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Post by HaRT »

John Doe wrote: Sat 24 May, 2025 19:51 readkey есть в репозитории LuaShell
Тут? Я там вижу только один Lua-файл, но в нём нет вызова ExtractKeyEx…
19:04 UTC: А, нашёл LuaShell.core и LuaShell.std на ГитХабе. Смотрю…
Last edited by HaRT on Sat 24 May, 2025 20:04, edited 1 time in total.
Фар есть инструмент, а не нянька. © 2009 DrKnS
User avatar
John Doe
Бюрократ
Posts: 14580
Joined: Wed 27 Apr, 2005 20:42
Location: github.com/FarManagerLegacy
Has thanked: 92 times
Been thanked: 514 times
Contact:

win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Post by John Doe »

HaRT wrote: Sat 24 May, 2025 20:01 Тут?
Там уже давно не обновлялось, надо удалить.
Не идёт у меня Fossil для чего-либо сложного, мне rebase нужен.
https://t.me/FarManager — Telegram чат
User avatar
HaRT
Moderator
Posts: 11454
Joined: Tue 30 Aug, 2005 17:21
Has thanked: 270 times
Been thanked: 417 times

win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Post by HaRT »

John Doe wrote: Sat 24 May, 2025 19:51 readkey есть в репозитории LuaShell
Ага, нашёл в LuaShell.std.
John Doe wrote: Sat 24 May, 2025 20:12 Не идёт у меня Fossil для чего-либо сложного, мне rebase нужен.
А я про этот Fossil узнал буквально вчера, когда решил обновить наконец LuaCheck. :)
Фар есть инструмент, а не нянька. © 2009 DrKnS
User avatar
John Doe
Бюрократ
Posts: 14580
Joined: Wed 27 Apr, 2005 20:42
Location: github.com/FarManagerLegacy
Has thanked: 92 times
Been thanked: 514 times
Contact:

win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Post by John Doe »

HaRT wrote: Sat 24 May, 2025 20:28Ага, нашёл
Выложенная выше версия ещё не обновлена в репозитории.
Разница главным образом в формате вывода: фаровские названия клавиш выводятся для тех сочетаний, которые распознаются фаром, и только для них
https://t.me/FarManager — Telegram чат
User avatar
John Doe
Бюрократ
Posts: 14580
Joined: Wed 27 Apr, 2005 20:42
Location: github.com/FarManagerLegacy
Has thanked: 92 times
Been thanked: 514 times
Contact:

win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Post by John Doe »

Пришлось доработать, чтобы правильно обрабатывалось одиночное нажатие AltGr.
Однако, есть небольшое отличие с mf.waitkey при определении названия сочетания клавиш AltGr и RCtrl
Key Combination | mf.waitkey | наша функция -------------------------|-------------------|--------------------- AltGr + RCtrl | RCtrlRAlt | CtrlRCtrlRAlt RCtrl + AltGr | CtrlRCtrlRAlt | RCtrlRAlt
Т.е. в данном случае работают ровно наоборот.
Почему наша функция именно так работает мне понятно.
А что за превращения происходят в фаре - сие понять из keyboard.cpp сложновато.
https://t.me/FarManager — Telegram чат
User avatar
John Doe
Бюрократ
Posts: 14580
Joined: Wed 27 Apr, 2005 20:42
Location: github.com/FarManagerLegacy
Has thanked: 92 times
Been thanked: 514 times
Contact:

win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Post by John Doe »

Как выяснилось, есть разница в сырых записях от win.ExtractKeyEx, и полученными через апи фара: фар (начиная с 6330) "нейтрализует" AltGr, чтобы по AltGr+Key приходили комбинации RAltKey, а не CtrlRAltKey.

Это несложно повторить и для записей полученных win.ExtractKeyEx, но сначала нужно определить, содержит ли текущая раскладка AltGr.
(Код пока не привожу, но там надо будет применить ffi)
https://t.me/FarManager — Telegram чат
User avatar
John Doe
Бюрократ
Posts: 14580
Joined: Wed 27 Apr, 2005 20:42
Location: github.com/FarManagerLegacy
Has thanked: 92 times
Been thanked: 514 times
Contact:

win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Post by John Doe »

John Doe wrote: Sat 24 May, 2025 19:06 Бонус: утилита для отображения клавиатурных событий.
Теперь независимые сочетания клавиш разделяются пустой строкой.
(Сочетание завершается после отпускания всех клавиш.)
https://t.me/FarManager — Telegram чат
User avatar
John Doe
Бюрократ
Posts: 14580
Joined: Wed 27 Apr, 2005 20:42
Location: github.com/FarManagerLegacy
Has thanked: 92 times
Been thanked: 514 times
Contact:

win.ExtractKeyEx и другие (win.ExtractKey, mf.waitkey, ...)

Post by John Doe »

На Github лежит патч улучшающий обработку комбинаций с AltGr.
По сути он "исправляет" определение комбинаций "RAlt", "AltRAlt", "RAltShift" - в противном случае в них попадает ещё и "Ctrl" (во всех других случаях сейчас фар успешно его убирает).
Помимо прочего это вредит макросам в следующих случаях:
  • Нарушается консистентность если AltGr не во всех раскладках.
  • Нет возможности различить нажатие "CtrlRAlt" и одиночного "RAlt".
Патч висит без движения уже 4 недели, и неизвестно заинтересует ли вообще кого-то из FarGroup, поэтому набросал костылик, решающий проблему локально, только для макросов:

Code: Select all

-- Alternative to https://github.com/FarGroup/FarManager/pull/965
F = far.Flags
local key
Macro
  description: "fix: AltGr -> RAlt"
  area: "Common"
  key: "LCtrlRAlt"
  condition: () ->
    win.Sleep 10
    if rec = win.ExtractKeyEx!
      if rec.VirtualKeyCode==18
        key = (0==bit64.band rec.ControlKeyState, F.LEFT_ALT_PRESSED) and "RAlt" or "AltRAlt"
        return 70
  id: "E5FABFD8-25A0-4018-8F52-22C0D575C5B7"
  action: -> mf.eval key, 2

Macro
  description: "fix: Shift+AltGr -> RAltShift"
  area: "Common"
  key: "LCtrlRAltShift"
  condition: () ->
    win.Sleep 10
    if rec = win.ExtractKeyEx!
      if rec.VirtualKeyCode==18
        return 70
  id: "5FE64CB0-63C7-48A6-9ABB-5972145179EB"
  action: -> mf.eval "RAltShift", 2
https://t.me/FarManager — Telegram чат
Post Reply

Return to “Обсуждение макросов”