Çünki (хswitcher «proof of concept» mərhələsində) kifayət qədər konstruktiv rəylər aldı (xatırlatmaq lazımdır ki), sərbəst vaxtımı layihənin inkişafına sərf etməyə davam etdim. İndi istəyirəm ki, bir az sizin… İkinci addım tamamilə tanış olmayacaq: konfiqurasiya dizaynının təklifi/müzakirəsi.

Belə olur ki, normal proqramçılar bu bütün fırlanma qurğularını tənzimləməkdə dəlilik dərəcəsində cansıxıcıdır.
Boş danışmamaq üçün, içimizdə üzləşdiyim bir nümunə var.
Ümumiyyətlə yaxşı düşündürülmüş (və yaxşı həyata keçirilmiş) Apache Kafka & ZooKeeper.
— Konfiqurasiya? Amma bu, çox cansıxıcıdır! Tül-tül xml (çünki "qutudan çıxanda").
— Eybi yox, siz ACL də istəyirsiniz? Amma bu, nə qədər üstün olur! Tül-tül… Belə bir şey.
Ancaq mənim işimdə — tam əksinə. Doğru (təzahür edərsə, demək olar ki, birinci dəfə deyil) qurulmuş model, sonrasında asanlıqla (yaxın, demək olar ki) sxemi toplamağa imkan verir.
Son vaxtlar Xabrdakı məlumatların çətin işləri haqda bir məqaləyə rast gəldim…
Məlum olur ki, bu moment tam şəkildə realizə olunur. Ancaq mənim təcrübəmdə, dediyimiz kimi, "yüngül versiya". Bir neçə cildli modellər, mükəmməl proqramçılar OOP ilə dolu və s. — bunlar hamısı sonra ortaya çıxacaq, nə vaxt/əgər havaya qalxarsa. Və konstruktora burada-hazırdan bir şey başlamaq lazımdır.
Məsələyə yaxınlaşaq. Sintaktik əsas olaraq TOML aldım .
Çünki o, (TOML) bir tərəfdən insan ilə redaktə edilə biləndir. Digər tərəfdən isə – hər hansı daha geniş yayılmış sintaksisə 1:1 şəkildə çevirilir: XML, JSON, YAML.
Bundan əlavə, istifadə etdiyim icra "github.com/BurntSushi/toml" olsa da, ən dəbli deyil (indiyə qədər sintaksis 1.4), amma sintaksiscə eyni "daxili" JSON ilə uyğundur.
Yəni, istədiyiniz halda sadəcə "gedin başımdan bu TOML ilə, mən XXX istəyirəm" deyə bilər və "bütün kodu bir sətirlə düzəldə" bilərsiniz.
Beləliklə, istədiyiniz halda хswitcher üçün konfiqurasiya etmək üçün bir neçə pəncərə yazmaq (şübhəsiz ki, mən deyil) problem «bu sizin alt-üst konfiqurasiyanızla» — görüləcək deyil.
Digər bütün sintaksis «açar = dəyər» (və hətta bir az çətin olan bir neçə seçim, məsələn = [bir neçə, ədədi]) düşünürəm
intuisiya ilə əlverişlidir.
Qeyri-adi olan, özü təxminən eyni zamanda (2013-cü illərdə). Təkcə, məndən fərqli olaraq, TOML-in yazarı bu işləri daha geniş apardı.
Buna görə indi onun reallığını özüm üçün uyğunlaşdırmaq daha asandır.
Ümumiyyətlə, TOML alırıq (köhnə Windows INI-yə çox bənzəyir). Və bir konfiqurasiya quraq, hansı ki sonuncu klaviatura sken kodlarına görə bir sıra hook-ları necə qoşmağı izah edir. Aşağıda parça-parça — indiyə qədər əldə olunan. Və bu sərf etdiyim səbəblərin izahları.
0. Əsas abstractions
- Skem kodlarının işarələnməsi. Bununla mütləq bir şey etməliyik, çünki sadəcə rəqəm kodları tamamilə insan oxunmazdır (bu mənim özümün işidir). ).
«ecodes.go»-nu «golang-evdev»-dən çıxardım (əsas mənbəyə baş çəkməyə üşəndim, baxmayaraq ki, müəllifin burada heç bir problemi yoxdur). Biraz (şimdilik) tamamilə dəhşətli olanı düzəltdim. Məsələn «LEFTBRACE» → «L_BRACE». - Daha əlavə «halda olan düymələr» anlayışını təqdim etdim. Çünki istifadə olunan reguler qrammatika uzun pasajlara icazə vermir. (Lakin, minimal xərclərlə yoxlamağa imkan tanıyır. Əgər yalnız «birbaşa» qeydiyyatdan istifadə etsəniz.)
- Bina daxilində basılmalar üçün «deduplication» olacaq. Beləliklə, «təkrarlanma» halı 2 qeydə alınacaq. bir Öz təcrübəmdən qeydlər
1. Şablonların bölməsi
[Templates] # "@name@" ifadələri sadələşdirmək üçün
# Kəlmələr bu simvollardan ibarət ola bilər (regex)
"WORD" = "([0-9A-Z`;']|[LR]_BRACE|COMMA|DOT|SLASH|KP[0-9])"
İnsan dilinin sözdizimi ilə meydana gələn hissələrdən ibarətdir. (grafemlər aka «simvollar») haqqında (fərqli bir şeydir).? Какая-то ужасная «простыня». Поэтому сразу закладываю понятие «шаблона».
2. Nə etməliyik, bir şey basıldığında (növbəti sken kodu gəldiyində)
[ActionKeys]
# Düyməni topla və əmrlərin ardıcıllığı üçün testi et
# !!! Təkrarlanan kodlar (kod=2) hər düymə üçün bir dəfə toplanmalıdır!
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"]
# Toplanmış bütün düymələri, daxil olanı da daxil olmaqla, sil. Bu, standart hərəkətdir.
Drop = ["ESC", "-", "TAB", "ENTER", "KPENTER", "LINEFEED..POWER"]
# Hər hansı biri «aşağı» vəziyyətində olduqda, bu düymələr üçün əlavə xəritə saxlayın.
# Vəziyyət, hərəkətdəki «OFF:"|"ON:" şərtləri ilə yoxlanılır.
# (Həmçinin, bu düymələrin vəziyyəti tampon düşüşləri arasında davam etməlidir.)
# ??? CAPS və «LOCK» düymələri ilə necə işləməliyik???
StateKeys = ["L_CTRL", "L_SHIFT", "L_ALT", "L_META", "CAPS", "N_LOCK", "S_LOCK",
"R_CTRL", "R_SHIFT", "R_ALT", "R_META"]
# Yalnız testdir, amma yığmağı etmir.
# Məsələn, mən dumb laptoplarda F12-dən BREAK əvəzinə istifadə edirəm (yeni ThinkPad-lar).
Test = ["F1..F10", "ZENKAKUHANKAKU", "102ND", "F11", "F12",
"RO..KPJPCOMMA", "SYSRQ", "SCALE", "HANGEUL..YEN",
"STOP..SCROLLDOWN", "NEW..MAX"]
Ümumilikdə 768 kod nəzərdə tutulmuşdur. (Amma «hər ehtimala qarşı» xswitcher koduna «sürprizlər» tutması əlavə etdim).
Daxildə «nə etməli olduğuna» dair funksiyalara linklərin doldurulmasını izah etdim. Golang-da bu (gözlənilmədən) uyğun və açıq-aydın olduğu ortaya çıxdı.
- Bu yerdə «Drop»-u minimuma endirməyi planlaşdırıram. Daha çevik emalın xeyrinə (aşağıda göstərəcəyəm).
3. Pəncərə sinifləri üçün cədvəl
# 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"
Cədvəl sətirləri — ikili kvadrat mötərizələrlə onun adı ilə. İlk addımda daha asan olmadı. Hazırda aktiv pəncərəyə görə, variantları seçmək mümkündür:
- Öz «isti düymələr» seti «Actions = …». Əgər yoxdur / boştur — heç nə etmə.
- «MouseClickDrops» keçidi — siçan klikini aşkar edərkən nə etməliyik. Çünki xswitcher-in açılış yerində «nəyi kliklədikləri» haqqında heç bir əlverişli məlumat yoxdur, standart olaraq tamponu sıfırlayırıq. Lakin terminalarda (məsələn) bunu etməyə bilərik. (məsələn).
4. Bir (və ya bir neçə) basış ardıcıllığı bir və ya digər hukun işə salınmasını təmin edir.
# 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)" ]
Hukları iki tipə ayırdım. İntegral olanlar, «düşünən» adlarla (NewWord, NewSentence, Compose) və proqramlaşdırıla bilənlər.
Proqramlaşdırıla bilənlərin adları «Action.» ilə başlayır. TOML v1.4-ə görə, nöqtə ilə olan adlar tırnaqlar içində olmalıdır.
Aşağıda hər biri üçün bölmə təsvir edilməlidir eyni başlıqla.
İnsanların beynini "boş" regular ifadələrlə (təcrübəyə görə, onların yazmaqbəlkə də on nəfərdən biri peşəkarlarından), dərhal əlavə sintaksisi tətbiq edirəm.
- "OFF:" (və ya "ON:") regexp (regular ifadəsi) qabağında tələb edir ki, sonrakı düymələr buraxılsın (və ya basılsın).
Daha sonra "ədalətsiz" bir regular ifadə yazmağı planlayıram. "|" arasında parçaların ayrıca yoxlanılması ilə. "[LR]_SHIFT" kimi qeydlərin sayını azaltmaq məqsədilə (orada bu aydın şəkildə lazım deyil). - "SEQ:" Əgər əvvəlki şərt yerinə yetirilmişdirsə (və ya yoxdur), daha sonra "adi" regular ifadəyə görə yoxlayırıq. Ətraflı məlumat üçün dərhal "regexp" kitabxanasına göndərirəm. Çünki hələ də sevdiyim pcre ilə ("perl uyumlu") uyğunluq səviyyəsini başa düşməyə vaxtım olmayıb.
- İfadə aşağıdakı kimi yazılır "BUTTON_1: CODE1, BUTTON_2: CODE2" və s., sken kodlarının gəliş sırasına uyğun.
- Yoxlamalar həmişə sıra sonuna "yığılır", buna görə "$" sonluğa əlavə edilmir.
- Bütün yoxlamalar bir sətirdə bir-birinin ardınca həyata keçirilir və "VƏ" ilə birləşdirilir. Lakin dəyər massiv şəklində təsvir edildiyindən, vergülden sonra alternativ yoxlama yazmaq mümkündür. Əgər bu hər hansı bir səbəbdən lazımdırsa.
- Dəyər "SeqLength = 8" bütün yoxlamaların həyata keçirildiyi tamponun ölçüsünü məhdudlaşdırır. Çünki real həyatda (hələ də) sonsuz resurslara rast gəlmədim.
5. Əvvəlki bölmədə qeyd olunan hook-ların təyini
# 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.
Burada əsas olan "Action = [Massiv]". Əvvəlki bölmədə olduğu kimi, məhdud sayda daxili fəaliyyət var. Və təməl prinsipcə bağlantı üçün məhdudiyyət yoxdur ("Action.XXX" yazın və ona uyğun başqa bir bölmə yazmağı çəkinməyin).
Bundan əlavə, düzəlişlə yazılmış sözdəki yenidən yazma iki hissəyə bölünür: "oradakı kimi düzülüşü dəyiş" və "yenidən yaz" ("RetypeWord").
Qalan parametrlər "sözlük"ə yazılır ("map" golang-da) bu fəaliyyət üçün, onların siyahısı "Action"-da yazılanlardan asılıdır.
Fərqli fəaliyyətlər bir yerdə təsvir edilə bilər (bölmələr). Və ya yaymaq olar. Yuxarıda göstərdiyim kimi.
Dərhal "Exec" fəaliyyəti qoyuram - xarici skripti yerinə yetirmək. Standard girişinə yazılmış tamponu ötürmək üçün seçim ilə.
- "Wait = 1" - başlatılmış prosesin tamamlanmasını gözləyin.
- Bəlkə də "yolda" pəncərəni tutulan sinifin adını əlavə etmək üçün əlavə məlumat vermək istəyə bilərsiniz.
"Öz işləyicinizi qoşmaq istəyirsiniz? Siz burada olacaqsınız."
Uff (nəfəsləndim). Görünür, heç nəyi unutmamışam.
Ops! Aha, unutmamışam...
Başlatma konfiqurasiyası harada? Həddən artıq kodda? Təxminən belə:
[ScanDevices]
# Başlanğıcda mövcud olmalıdır. 30s-dan gənc olarsa, öz-özünə yenidən başlayır
Test = "\/dev\/input\/event0"
Respawn = 30
# Axtarış maskası
Search = "\/dev\/input\/event*"
# Mənim ThinkPad-larımda bu cür çoxsaylı lazımsız hadisələrlə birlikdə belə pseudo-klaviaturalar var
Bypass = "(?i)Video|Camera" # "(?i)" açıq-aydın "klassik" pcre-dən fərqlənir.
Harada unutmuşam\/səhv etdim (bununla — olmaz), çox ümid edirəm ki, diqqətli oxuyucular tənbəllik etməyib burnunu göstərirlər.
Uğurlar!
Mənbə: habr.com
