Linux üçün Xswitcher layout korrektoru: ikinci addım

Kimi əvvəlki nəşr (“konseptin sübutu” mərhələsində xswitcher) kifayət qədər çox konstruktiv rəy aldı (bu gözəldir), boş vaxtlarımı layihənin hazırlanmasına sərf etməyə davam etdim. İndi mən sizin bir az xərcləmək istəyirəm... İkinci addım olduqca adi olmayacaq: konfiqurasiya dizaynının təklifi/müzakirəsi.

Linux üçün Xswitcher layout korrektoru: ikinci addım

Nədənsə belə çıxır ki, adi proqramçılar bütün bu idarəetmələri quraşdırmağı inanılmaz dərəcədə darıxdırıcı hesab edirlər.

Əsassız olmasın deyə, içimdə nə ilə məşğul olduğum bir nümunədir.
Ümumilikdə əla düşünülmüş (və yaxşı tətbiq edilmiş) Apache Kafka və ZooKeeper.
- Konfiqurasiya? Amma darıxdırıcıdır! Lal xml ("qutudan kənarda" olduğu üçün).
- Oh, siz də ACL istəyirsiniz? Amma çox darıxdırıcıdır! Tap-blooper... Belə bir şey.

Amma mənim işimdə bunun tam əksi var. Sağ (təəssüf ki, ilk dəfə demək olar ki, heç vaxt) qurulmuş model daha asan və təbii şəkildə davam etməyə imkan verir (Təxminən) diaqramı yığın.

Bu yaxınlarda Habré-də məlumat alimlərinin zəhməti haqqında bir məqaləyə rast gəldim...
Belə çıxır ki, bu an onlar üçün tam reallaşıb. Mənim praktikamda, necə deyərlər, "yüngül versiya". Çoxcildli modellər, hazır OOP olan təcrübəli proqramçılar və s. — bütün bunlar daha sonra havaya qalxdıqda/əgər görünəcək. Amma dizayner burada və indi haradansa başlamalıdır.

Nöqtəyə gəlin. TOML-i sintaktik əsas kimi götürdüm bu vətəndaşdan.

Çünki o (TOML) bir tərəfdən, insan tərəfindən redaktə edilə bilər. Digər tərəfdən, 1:1 nisbətində daha çox yayılmış sintaksislərdən hər hansı birinə tərcümə olunur: XML, JSON, YAML.
Üstəlik, “github.com/BurntSushi/toml” saytından istifadə etdiyim tətbiq, ən dəbli olmasa da (hələ 1.4 sintaksis), sintaktik olaraq eyni (“daxili”) JSON ilə uyğun gəlir.

Yəni, istəsəniz, sadəcə olaraq “o TOML-inizlə meşədən keçin, XXX istəyirəm” deyə və kodu bir sətirlə “yamaq” edə bilərsiniz.

Beləliklə, xswitcher-i konfiqurasiya etmək üçün bəzi pəncərələr yazmaq istəyirsinizsə (Əmin deyiləm) “Bu lənətə gəlmiş konfiqurasiyanızla” heç bir problem gözlənilmir.

Bütün digərləri üçün sintaksis "açar = dəyər" əsasında qurulur. (və sözün əsl mənasında bir neçə daha mürəkkəb variant, məsələn = [bəzi, o, massiv]) Məncə
intuitiv olaraq rahatdır.
Maraqlısı budur "yandırılmış" təxminən eyni vaxtda (təxminən 2013). Yalnız məndən fərqli olaraq, TOML müəllifi lazımi miqyasda daxil oldu.

Buna görə də, indi onun həyata keçirilməsini özümə uyğunlaşdırmaq mənim üçün daha asandır, əksinə deyil.

Ümumiyyətlə, biz TOML alırıq (köhnə Windows INI-yə çox oxşar). Klaviaturadan ən son skan kodlarının dəstindən asılı olaraq bir sıra qarmaqların necə bağlanacağını təsvir etdiyimiz bir konfiqurasiyamız var. Aşağıda, parça-parça, indiyə qədər baş verənlər. Və niyə belə qərar verdiyimin izahı.

0. Əsas abstraksiyalar

  • Skan kodu təyinatları. Bununla bağlı mütləq bir şey etmək lazımdır, çünki sadəcə rəqəmsal kodlar insanlar tərəfindən oxunmur (bu, sadəcə mənəm) loloswitcher).
    “golang-evdev”dən “ecodes.go”nu silkələdim (orijinal mənbəyə baxmaq üçün çox tənbəl idim, baxmayaraq ki, müəllif bunu kifayət qədər mədəni şəkildə göstərmişdir). Mən olduqca qorxulu bir şeyi bir az (hazırda) düzəltdim. “LEFTBRRACE” → “L_BRACE” kimi.
  • Bundan əlavə, o, "dövlət açarları" anlayışını təqdim etdi. İstifadə olunan adi qrammatika uzun keçidlərə imkan vermədiyi üçün. (Lakin bu, minimum yüklə yoxlamağa imkan verir. Yalnız “birbaşa” qeyddən istifadə edirsinizsə.)
  • Basılanların daxili "deuplikatoru" olacaq. Beləliklə, "təkrar" = 2 vəziyyəti yazılacaq bir dəfə

1. Şablonlar bölməsi

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

İnsan dilində fonetik qeydi olan söz nədən ibarətdir? (ya da “heroqliflər” kimi tanınan qrafemlər məsələsi)? Bir növ dəhşətli "vərəq". Ona görə də mən dərhal “şablon” anlayışını təqdim edirəm.

2. Nəyisə kliklədikdə nə etməli (başqa bir skan kodu gəldi)

[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"]

Cəmi 768 kod var. (Ancaq "hər halda" xswitcher koduna "sürprizlər" əlavə etdim).
İçəridə massivi “nə etməli” funksiyalarına keçidlərlə doldurmağı təsvir etdim. Qolanqda belədir (birdən) Rahat və aydın olduğu ortaya çıxdı.

  • Mən bu yerdə “Düşməni” minimuma endirməyi planlaşdırıram. Daha çevik emalın lehinə (aşağıda göstərəcəyəm).

3. Pəncərə sinifləri olan 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əlin sətirləri adı ilə ikiqat kvadrat mötərizə içərisindədir. Yarasadan daha asan ola bilməzdi. Hazırda aktiv pəncərədən asılı olaraq aşağıdakı seçimləri seçə bilərsiniz:

  • Öz "isti düymələr" dəstiniz "Fəaliyyətlər = ...". Boş deyilsə, heç nə etməyin.
  • “MouseClickDrops”a keçin - siçan kliklənməsi aşkar edildikdə nə etməli. Xswitcher-in işə salındığı nöqtədə “hara kliklədikləri” haqqında heç bir təfərrüat olmadığı üçün biz buferi standart olaraq sıfırlayırıq. Ancaq terminallarda (məsələn) bunu etmək lazım deyil (adətən).

4. Bir (və ya bir neçə) klik ardıcıllığı bu və ya digər qarmaqları işə salır

# 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)" ]

Qarmaqlar iki növə bölünür. Daxili, “danışan” adlarla (NewWord, NewSentence, Compose) və proqramlaşdırıla bilir.

Proqramlaşdırıla bilən adlar "Fəaliyyət" ilə başlayır. Çünki TOML v1.4, nöqtəli adlar dırnaq içərisində olmalıdır.

Hər bir bölmə aşağıda təsvir edilməlidir eyni adla.

İnsanların ağlını “çılpaq” müntəzəmlərlə (təcrübədən, onların yazmaqbəlkə ondan biri peşəkarlar), dərhal əlavə sintaksisi həyata keçirirəm.

  • "OFF:" (və ya "ON:") regexp-dən əvvəl (müntəzəm ifadə) aşağıdakı düymələrin buraxılmasını (və ya basılmasını) tələb edir.
    Sonra “ədalətsiz” müntəzəm ifadə işlədəcəyəm. Borular arasında parçaların ayrıca yoxlanılması ilə "|". "[LR]_SHIFT" kimi qeydlərin sayını azaltmaq üçün (burada bu, aydın şəkildə lazım deyil).
  • "SEQ:" Əgər əvvəlki şərt yerinə yetirilibsə (və ya yoxdursa), onda biz “normal” normal ifadə ilə yoxlayırıq. Ətraflı məlumat üçün mən dərhal ^W-yə “regexp” kitabxanasını göndərirəm. Çünki sevdiyim pcre (“perl uyğun”) ilə uyğunluq dərəcəsini öyrənmək üçün hələ də narahat olmamışam.
  • İfadə formada yazılır "BUTTON_1: CODE1, BUTTON_2: CODE2" skan kodlarının alınma ardıcıllığı ilə və s.
  • Çek həmişə ardıcıllığın sonuna "bağlanır", ona görə də quyruğa “$” əlavə etməyə ehtiyac yoxdur.
  • Bir xəttdəki bütün yoxlamalar bir-birinin ardınca aparılır və “mən” ilə birləşir. Lakin dəyər massiv kimi təsvir olunduğu üçün vergüldən sonra alternativ çek yaza bilərsiniz. Əgər bu nədənsə lazımdırsa.
  • Dəyər "SeqLength = 8" bütün yoxlamaların aparıldığı buferin ölçüsünü məhdudlaşdırır. Çünki Mən (indiyə qədər) həyatımda heç vaxt sonsuz resurslarla qarşılaşmamışam.

5. Əvvəlki bölmədə təsvir edilən qarmaqların quraşdırılması

# 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 şey budur "Fəaliyyət = [Array]". Əvvəlki bölməyə bənzər olaraq, məhdud daxili hərəkətlər dəsti var. Və docking imkanı prinsipcə məhdud deyil (“Action.XXX” yazın və bunun üçün başqa bölmə yazmağa tənbəl olmayın).
Xüsusilə, sözün düzəldilmiş tərtibatda yenidən yazılması iki hissəyə bölünür: "orada göstərildiyi kimi düzeni dəyişdirin" и “yenidən yazın” (“Yenidən yazın”).

Qalan parametrlər "lüğətə" yazılır. (qolanq dilində "xəritə") müəyyən bir hərəkət üçün onların siyahısı “Fəaliyyət”də yazılanlardan asılıdır.

Bir yığında bir neçə fərqli hərəkət təsvir edilə bilər (bölmələr). Və ya onu ayıra bilərsiniz. Yuxarıda göstərdiyim kimi.

Xarici skripti yerinə yetirmək üçün dərhal "Exec" hərəkətini təyin etdim. Qeydə alınmış buferi stdin-ə itələmək imkanı ilə.

  • “Gözləyin = 1” — çalışan prosesin tamamlanmasını gözləyin.
  • Yəqin ki, "yığına" ətrafa əlavə insanları yerləşdirmək istəyəcəksiniz. onun tutulduğu pəncərə sinfinin adı kimi məlumatlar.
    “İşləyicinizi birləşdirmək istəyirsiniz? Bura getmək lazımdır”.

Eh (nəfəs aldı). Deyəsən heç nəyi unutmamışam.

Vay! Hə, unutmadım...
Başlatma konfiqurasiyası haradadır? Sərt kodda? Bunun kimi:

[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.

Harada unutdum/səhv etdim? (bu olmadan yol yoxdur), həqiqətən ümid edirəm ki, diqqətli oxucular burunlarını soxmaq üçün çox tənbəl olmayacaqlar.

Uğurlar!

Mənbə: www.habr.com

Добавить комментарий