Linux uchun Xswitcher tartibini tuzatuvchisi: ikkinchi qadam

chunki oldingi nashr (xswitcher "kontseptsiyani isbotlash" bosqichida) juda ko'p konstruktiv fikr-mulohazalarni oldi (bu yaxshi), Men bo'sh vaqtimni loyihani ishlab chiqishda davom ettirdim. Endi men sizning pulingizdan bir oz sarflamoqchiman ... Ikkinchi bosqich odatiy bo'lmaydi: konfiguratsiya dizayni bo'yicha taklif/muhokama.

Linux uchun Xswitcher tartibini tuzatuvchisi: ikkinchi qadam

Qanday bo'lmasin, oddiy dasturchilar ushbu boshqaruv elementlarini o'rnatishni juda zerikarli deb bilishadi.

Asossiz bo'lmaslik uchun, ichkarida men nima bilan shug'ullanayotganimga misol.
Umuman olganda, Apache Kafka va ZooKeeper juda yaxshi ishlab chiqilgan (va yaxshi amalga oshirilgan).
- Konfiguratsiya? Lekin bu zerikarli! Soqov xml (chunki u "qutidan tashqarida").
- Oh, siz ham OChLni xohlaysizmi? Lekin bu juda zerikarli! Tap-blooper... Shunga o'xshash narsa.

Lekin mening ishimda buning aksi. To'g'ri (afsuski, birinchi marta deyarli hech qachon) qurilgan model yanada oson va tabiiy davom ettirish imkonini beradi (deyarli) diagramma yig'ish.

Men yaqinda Habré-da ma'lumotlar olimlarining mashaqqatli mehnati haqida maqolaga duch keldim ...
Ma'lum bo'lishicha, bu lahza ular uchun to'liq amalga oshirilgan. Va mening amaliyotimda, ular aytganidek, "engil versiya". Ko'p jildli modellar, OOP ga ega tajribali dasturchilar va boshqalar. — bularning barchasi keyinroq havoga ko'tarilganda/agar paydo bo'ladi. Lekin dizayner bu erda va hozir bir joydan boshlashi kerak.

Gapga keling. Men TOMLni sintaktik asos sifatida oldim bu fuqarodan.

Chunki u (TOML) bir tomondan, inson tomonidan tahrirlanishi mumkin. Boshqa tomondan, u 1:1 ko'rinishida keng tarqalgan sintaksislarning har qandayiga tarjima qilinadi: XML, JSON, YAML.
Bundan tashqari, men "github.com/BurntSushi/toml" dan foydalangan dastur eng zamonaviy bo'lmasa ham (hali 1.4 sintaksisi), sintaktik jihatdan bir xil ("o'rnatilgan") JSON bilan mos keladi.

Ya'ni, agar xohlasangiz, oddiygina "o'zingizning TOMLingiz bilan o'rmondan o'ting, men XXXni xohlayman" deb aytishingiz va kodni faqat bitta satr bilan "yamoq" qilishingiz mumkin.

Shunday qilib, agar siz xswitcherni sozlash uchun ba'zi oynalarni yozmoqchi bo'lsangiz (Aniq emas; ishonchim komil emas) "Sizning la'nati konfiguratsiyangiz bilan" hech qanday muammo kutilmaydi.

Boshqalar uchun sintaksis "kalit = qiymat" ga asoslangan. (va tom ma'noda bir nechta murakkab variantlar, masalan = [ba'zi, bu, massiv]) Mening nazarimda
intuitiv jihatdan qulay.
Qizig'i shundaki "kuygan" taxminan bir vaqtning o'zida (taxminan 2013 yil). Faqat, mendan farqli o'laroq, TOML muallifi tegishli miqyosda kirdi.

Shuning uchun, endi men uchun uni amalga oshirishni o'zimga moslashtirish osonroq, aksincha emas.

Umuman olganda, biz TOML ni olamiz (eski Windows INI ga juda o'xshash). Va bizda klaviaturadan so'nggi skanerlash kodlari to'plamiga qarab bir qator kancalarni qanday ulashni tasvirlaydigan konfiguratsiya mavjud. Quyida, parcha-parcha, hozirgacha nima bo'lgan. Va nima uchun bunday qarorga kelganimni tushuntirish.

0. Asosiy abstraktsiyalar

  • Kod belgilarini skanerlash. Bu borada aniq bir narsa qilish kerak, chunki oddiygina raqamli kodlar odamlar tomonidan o'qilmaydi (bu faqat men loloswitcher).
    Men “ecodes.go”ni “golang-evdev” dan chiqarib tashladim (asl manbaga qarashga dangasa bo‘ldim, garchi muallif buni madaniyatli tarzda ko‘rsatgan bo‘lsa ham). Men juda qo'rqinchli narsani biroz (hozircha) tuzatdim. “LEFBRACE” → “L_BRACE” kabi.
  • Bundan tashqari, u "davlat kalitlari" tushunchasini kiritdi. Qo'llaniladigan oddiy grammatika uzoq bo'laklarga ruxsat bermagani uchun. (Ammo bu sizga minimal qo'shimcha xarajatlar bilan tekshirish imkonini beradi. Agar siz faqat "to'g'ridan-to'g'ri" yozishdan foydalansangiz.)
  • Bosilgan narsaning o'rnatilgan "deduplikatori" bo'ladi. Shunday qilib, "takrorlash" = 2 holati yoziladi один marta.

1. Shablonlar bo'limi

[Templates] # "@name@" to simplify expressions
 # Words can consist of these chars (regex)
 "WORD" = "([0-9A-Z`;']|[LR]_BRACE|COMMA|DOT|SLASH|KP[0-9])"

Inson tilidagi fonetik belgilarga ega so‘z nimalardan iborat? (yoki "ierogliflar" deb nomlanuvchi grafemalar masalasi)? Qandaydir dahshatli "varaq". Shuning uchun men darhol "shablon" tushunchasini kiritaman.

2. Biror narsa bosilganda nima qilish kerak (boshqa skanerlash kodi keldi)

[ActionKeys]
 # Collect key and do the test for command sequence
 # !!! Repeat codes (code=2) must be collected once per key!
 Add = ["1..0", "=", "BS", "Q..]", "L_CTRL..CAPS", "N_LOCK", "S_LOCK",
        "KP7..KPDOT", "R_CTRL", "KPSLASH", "R_ALT", "KPEQUAL..PAUSE",
        "KPCOMMA", "L_META..COMPOSE", "KPLEFTPAREN", "KPRIGHTPAREN"]

 # Drop all collected keys, including this.  This is default action.
 Drop = ["ESC", "-", "TAB", "ENTER", "KPENTER", "LINEFEED..POWER"]
 # Store extra map for these keys, when any is in "down" state.
 # State is checked via "OFF:"|"ON:" conditions in action.
 # (Also, state of these keys must persist between buffer drops.)
 # ??? How to deal with CAPS and "LOCK"-keys ???
 StateKeys = ["L_CTRL", "L_SHIFT", "L_ALT", "L_META", "CAPS", "N_LOCK", "S_LOCK",
              "R_CTRL", "R_SHIFT", "R_ALT", "R_META"]

 # Test only, but don't collect.
 # E.g., I use F12 instead of BREAK on dumb laptops whith shitty keyboards (new ThinkPads)
 Test = ["F1..F10", "ZENKAKUHANKAKU", "102ND", "F11", "F12",
          "RO..KPJPCOMMA", "SYSRQ", "SCALE", "HANGEUL..YEN",
          "STOP..SCROLLDOWN", "NEW..MAX"]

Hammasi bo'lib 768 ta kod mavjud. (Ammo "har holda" men xswitcher kodiga "syurprizlar" ni kiritdim).
Ichkarida men massivni "nima qilish kerak" funktsiyalariga havolalar bilan to'ldirishni tasvirlab berdim. Golangda bu (birdan) Bu qulay va aniq bo'lib chiqdi.

  • Men bu joyda "Drop" ni minimal darajaga tushirishni rejalashtirmoqdaman. Ko'proq moslashuvchan ishlov berish foydasiga (men buni quyida ko'rsataman).

3. Oyna sinflari bilan jadval

# Some behaviour can depend on application currently doing the input.
[[WindowClasses]]
 # VNC, VirtualBox, qemu etc. emulates there input independently, so never intercept.
 # With the exception of some stupid VNC clients, which does high-level (layout-based) keyboard input.
 Regex = "^VirtualBox"
 Actions = "" # Do nothing while focus stays in VirtualBox

[[WindowClasses]]
 Regex = "^konsole"
 # In general, mouse clicks leads to unpredictable (at the low-level where xswitcher resides) cursor jumps.
 # So, it's good choise to drop all buffers after click.
 # But some windows, e.g. terminals, can stay out of this problem.
 MouseClickDrops = 0
 Actions = "Actions"

[[WindowClasses]] # Default behaviour: no Regex (or wildcard like ".")
 MouseClickDrops = 1
 Actions = "Actions"

Jadvalning satrlari ikki kvadrat qavs ichida uning nomi bilan yozilgan. Bu oson bo'lishi mumkin emas edi. Hozirda faol oynaga qarab siz quyidagi variantlarni tanlashingiz mumkin:

  • O'zingizning "issiq tugmalar" to'plami "Amallar = ...". Agar bo'lmasa/bo'sh bo'lsa, hech narsa qilmang.
  • "MouseClickDrops" ni o'zgartiring - sichqonchani bosish aniqlanganda nima qilish kerak. Xswitcher yoqilgan nuqtada "qaerga bosish" haqida hech qanday ma'lumot yo'qligi sababli, biz buferni sukut bo'yicha tiklaymiz. Lekin terminallarda (masalan,) buni qilish shart emas (Qoida sifatida).

4. Bir (yoki bir nechta) bosish ketma-ketligi u yoki bu kancani ishga tushiradi

# action = [ regex1, regex2, ... ]
# "CLEAN" state: all keys are released
[Actions]
# Inverse regex is hard to understand, so extract negation to external condition.
# Expresions will be checked in direct order, one-by-one. Condition succceds when ALL results are True.
 # Maximum key sequence length, extra keys will be dropped. More length - more CPU.
 SeqLength = 8
 # Drop word buffer and start collecting new one
 NewWord = [ "OFF:(CTRL|ALT|META)  SEQ:(((BACK)?SPACE|[LR]_SHIFT):[01],)*(@WORD@:1)", # "@WORD@:0" then collects the char
             "SEQ:(@WORD@:2,@WORD@:0)", # Drop repeated char at all: unlikely it needs correction
             "SEQ:((KP)?MINUS|(KP)?ENTER|ESC|TAB)" ] # Be more flexible: chars line "-" can start new word, but must not completelly invalidate buffer!
 # Drop all buffers
 NewSentence = [ "SEQ:(ENTER:0)" ]

 # Single char must be deleted by single BS, so there is need in compose sequence detector.
 Compose = [ "OFF:(CTRL|L_ALT|META|SHIFT)  SEQ:(R_ALT:1,(R_ALT:2,)?(,@WORD@:1,@WORD@:0){2},R_ALT:0)" ]

 "Action.RetypeWord" = [ "OFF:(CTRL|ALT|META|SHIFT)  SEQ:(PAUSE:0)" ]
 "Action.CyclicSwitch" = [ "OFF:(R_CTRL|ALT|META|SHIFT)  SEQ:(L_CTRL:1,L_CTRL:0)" ] # Single short LEFT CONTROL
 "Action.Respawn" = [ "OFF:(CTRL|ALT|META|SHIFT)  SEQ:(S_LOCK:2,S_LOCK:0)" ] # Long-pressed SCROLL LOCK

 "Action.Layout0" = [ "OFF:(CTRL|ALT|META|R_SHIFT)  SEQ:(L_SHIFT:1,L_SHIFT:0)" ] # Single short LEFT SHIFT
 "Action.Layout1" = [ "OFF:(CTRL|ALT|META|L_SHIFT)  SEQ:(R_SHIFT:1,R_SHIFT:0)" ] # Single short RIGHT SHIFT

 "Action.Hook1" = [ "OFF:(CTRL|R_ALT|META|SHIFT)  SEQ:(L_ALT:1,L_ALT:0)" ]

Kancalar ikki turga bo'linadi. O'rnatilgan, "gapirish" nomlari bilan (NewWord, NewSentence, Compose) va dasturlashtiriladi.

Dasturlashtiriladigan nomlar "Harakat" bilan boshlanadi. Chunki TOML v1.4, nuqtali nomlar tirnoq ichida bo'lishi kerak.

Har bir bo'lim quyida tavsiflanishi kerak xuddi shu nom bilan.

Odamlarning ongini "yalang'och" muntazamlar bilan urmaslik uchun (tajribadan, ularning yozingehtimol o'ndan bittasi professionallar), Men darhol qo'shimcha sintaksisni amalga oshiraman.

  • "OFF:" (yoki "ON:") regexp (muntazam ifoda) dan oldin quyidagi tugmalarni qo'yib yuborishni (yoki bosishni) talab qiladi.
    Keyin men "adolatsiz" muntazam iborani yarataman. Quvurlar orasidagi qismlarni alohida tekshirish bilan "|". "[LR]_SHIFT" kabi yozuvlar sonini kamaytirish uchun (bu aniq shart emas).
  • "SEQ:" Agar oldingi shart bajarilgan bo'lsa (yoki mavjud bo'lmasa), biz "oddiy" muntazam ifodani tekshiramiz. Tafsilotlar uchun men darhol ^W ga "regexp" kutubxonasini yuboraman. Chunki men hali ham sevimli pcre ("perl mos") bilan moslik darajasini bilish uchun bezovta qilmadim.
  • Ifoda shaklda yoziladi “BUTTON_1: CODE1, BUTTON_2: CODE2” va hokazo, skanerlash kodlari olingan tartibda.
  • Tekshiruv har doim ketma-ketlikning oxiriga qadar "yopiladi", shuning uchun quyruqga "$" qo'shishning hojati yo'q.
  • Bir qatordagi barcha tekshiruvlar birin-ketin amalga oshiriladi va "men" bilan birlashtiriladi. Ammo qiymat massiv sifatida tasvirlanganligi sababli, verguldan keyin muqobil chek yozishingiz mumkin. Agar bu biron bir sababga ko'ra kerak bo'lsa.
  • ma'no "SeqLength = 8" barcha tekshiruvlar amalga oshiriladigan bufer hajmini cheklaydi. Chunki Men (hozirgacha) hayotimda hech qachon cheksiz manbalarga duch kelmaganman.

5. Oldingi bo'limda tasvirlangan kancalarni o'rnatish

# Action is the array, so actions could be chained (m.b., infinitely... Have I to check this?).
# For each action type, extra named parameters could be collected. Invalid parameters will be ignored(?).
[Action.RetypeWord] # Switch layout, drop last word and type it again
 Action = [ "Action.CyclicSwitch", "RetypeWord" ] # Call Switch() between layouts tuned below, then RetypeWord()

[Action.CyclicSwitch] # Cyclic layout switching
 Action = [ "Switch" ] # Internal layout switcher func
 Layouts = [0, 1]

[Action.Layout0] # Direct layout selection
 Action = [ "Layout" ] # Internal layout selection func
 Layout = 0

[Action.Layout1] # Direct layout selection
 Action = [ "Layout" ] # Internal layout selection func
 Layout = 1

[Action.Respawn] # Completely respawn xswitcher. Reload config as well
 Action = [ "Respawn" ]

[Action.Hook1] # Run external commands
  Action = [ "Exec" ]
  Exec = "/path/to/exec -a -b --key_x"
  Wait = 1
  SendBuffer = "Word" # External hook can process collected buffer by it's own means.

Bu erda asosiy narsa "Harakat = [Masiv]". Oldingi bo'limga o'xshab, o'rnatilgan harakatlarning cheklangan to'plami mavjud. Va docking imkoniyati printsipial jihatdan cheklanmaydi (“Action.XXX” deb yozing va uning uchun boshqa bo‘lim yozishga dangasa bo‘lmang).
Xususan, tuzatilgan tartibda so'zni qayta yozish ikki qismga bo'linadi: "tartibni u erda ko'rsatilgandek o'zgartiring" и “qayta yozish” (“Qayta yozish”).

Qolgan parametrlar "lug'at" ga yoziladi. (golang tilida "xarita") berilgan harakat uchun ularning ro'yxati "Harakat" da nima yozilganiga bog'liq.

Bir to'plamda bir nechta turli harakatlar tasvirlanishi mumkin (bo'limlar). Yoki siz uni ajratib olishingiz mumkin. Yuqorida ko'rsatganimdek.

Men darhol tashqi skriptni bajarish uchun "Exec" amalini o'rnatdim. Yozilgan buferni stdinga surish imkoniyati bilan.

  • "Kutish = 1" - ishlaydigan jarayon tugashini kuting.
  • Ehtimol, "to'pga" siz atrof-muhitga qo'shimcha odamlarni qo'yishni xohlaysiz. ushlangan oyna sinfining nomi kabi ma'lumotlar.
    “Siz ishlov beruvchingizni ulashni xohlaysizmi? Bu erga borish kerak."

Voy (nafas chiqarilgan). Men hech narsani unutmaganga o'xshayman.

Voy! Ha, unutmadim...
Ishga tushirish konfiguratsiyasi qayerda? Qattiq kodda? Shunga o'xshash:

[ScanDevices]
 # Must exist on start. Self-respawn in case it is younger then 30s
 Test = "/dev/input/event0"
 Respawn = 30
 # Search mask
 Search = "/dev/input/event*"
 # In my thinkPads there are such a pseudo-keyboards whith tons of unnecessary events
 Bypass = "(?i)Video|Camera" # "(?i)" obviously differs from "classic" pcre's.

Qayerda unutdim/xato qildim? (busiz yo'q), Umid qilamanki, diqqatli o'quvchilar burunlarini teshishga dangasa bo'lmaydilar.

Omad tilaymiz!

Manba: www.habr.com

a Izoh qo'shish