Linux için Xswitcher düzen düzelticisi: ikinci adım

beri önceki yayın (xswitcher “kavramın kanıtlanması” aşamasında) oldukça fazla yapıcı geri bildirim aldı (ki bu güzel)Boş zamanlarımı projeyi geliştirmeye ayırmaya devam ettim. Şimdi senin birazını harcamak istiyorum... İkinci adım pek alışılagelmiş bir adım olmayacak: konfigürasyon tasarımının teklif edilmesi/tartışılması.

Linux için Xswitcher düzen düzelticisi: ikinci adım

Bir şekilde normal programcıların tüm bu kontrolleri ayarlamayı inanılmaz derecede sıkıcı buldukları ortaya çıktı.

Asılsız olmamak adına içeride uğraştığım şeyin bir örneği var.
Genel olarak mükemmel bir şekilde tasarlanmış (ve iyi uygulanmış) Apache Kafka ve ZooKeeper.
- Yapılandırma? Ama çok sıkıcı! Aptal xml (çünkü "kutudan çıktığı gibi").
- Sen de ACL istiyor musun? Ama çok sıkıcı! Vuruş hatası... Bunun gibi bir şey.

Ama benim işimde durum tam tersi. Sağ (ne yazık ki, neredeyse hiçbir zaman ilk seferde) Oluşturulan model daha kolay ve doğal bir şekilde devam etmenizi sağlar (Neredeyse) bir diyagram oluşturun.

Geçenlerde Habré'de veri bilimcilerin sıkı çalışmasıyla ilgili bir makaleye rastladım...
Bu anın onlar için tamamen gerçekleştiği ortaya çıktı. Ve benim pratiğimde dedikleri gibi "hafif versiyon". Çok hacimli modeller, hazır OOP'lu deneyimli programcılar vb. — bunların hepsi daha sonra havalandığında/kalkışta ortaya çıkacak. Ancak tasarımcının burada ve şimdi bir yerden başlaması gerekiyor.

Asıl noktaya gelin. TOML'u sözdizimsel bir temel olarak aldım bu vatandaştan.

Çünkü o (TOML) bir yandan insan tarafından düzenlenebilir. Öte yandan, daha yaygın sözdizimlerinden herhangi birine 1:1 oranında çevrilir: XML, JSON, YAML.
Üstelik, "github.com/BurntSushi/toml" adresinden kullandığım uygulama, en modası olmasa da (hala 1.4 sözdizimi), sözdizimsel olarak aynı ("yerleşik") JSON ile uyumludur.

Yani dilerseniz “şu TOML'ünüzle ormana gidin, XXX istiyorum” deyip kodu tek satırla “yama” yapabilirsiniz.

Bu nedenle, xswitcher'ı yapılandırmak için bazı pencereler yazmak istiyorsanız (Emin değilim) "Bu lanet yapılandırmanızla" hiçbir sorun beklenmiyor.

Diğerleri için sözdizimi “anahtar = değer” temeline dayanmaktadır (ve kelimenin tam anlamıyla = [some, that, array] gibi birkaç daha karmaşık seçenek) sanırım
Sezgisel olarak kullanışlı.
İlginç olan şu ki "yanmış" yaklaşık aynı zamanlarda (yaklaşık 2013). Ancak benden farklı olarak TOML'un yazarı uygun bir ölçeğe girdi.

Bu nedenle, uygulamasını kendime göre ayarlamak artık benim için daha kolay, tersi değil.

Genel olarak TOML'ü alıyoruz (eski Windows INI'ye çok benzer). Ve klavyedeki en son tarama kodları setine bağlı olarak bir dizi kancanın nasıl takılacağını açıkladığımız bir konfigürasyonumuz var. Aşağıda şu ana kadar yaşananlar parça parça yer alıyor. Ve neden bu şekilde karar verdiğimin bir açıklaması.

0. Temel soyutlamalar

  • Kod tanımlarını tarayın. Bu konuda kesinlikle bir şeyler yapılması gerekiyor, çünkü dijital kodlar kesinlikle insanlar tarafından okunamaz (bu sadece benim fikrim) loloswitcher).
    "golang-evdev"den "ecodes.go"yu salladım (yazar bunu oldukça kültürel olarak belirtmesine rağmen orijinal kaynağa bakamayacak kadar tembeldim). Oldukça korkutucu olan bir şeyi (şimdilik) biraz düzelttim. “LEFTBRACE” → “L_BRACE” gibi.
  • Ayrıca “durum anahtarları” kavramını da tanıttı. Kullanılan normal dilbilgisi uzun pasajlara izin vermediğinden. (Ancak minimum ek yük ile kontrol yapmanıza olanak tanır. Yalnızca "doğrudan" kayıt kullanıyorsanız.)
  • Basılan şeyin yerleşik bir "tekrarlayıcısı" olacaktır. Böylece "tekrarla"=2 durumu yazılacaktır biri kez.

1. Şablonlar bölümü

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

Fonetik gösterime sahip bir insan dili kelimesi nelerden oluşur? (ya bir grafik meselesi, diğer adıyla “hiyeroglif”)? Bir tür korkunç "çarşaf". Bu nedenle hemen “şablon” kavramını tanıtıyorum.

2. Bir şeye tıklandığında ne yapılmalı (başka bir tarama kodu geldiğinde)

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

Toplamda 768 kod bulunmaktadır. (Ama "her ihtimale karşı" xswitcher koduna "sürprizler" yakalayıcı şeyler ekledim).
İçeride diziyi "ne yapılması gerektiği" işlevlerine bağlantılarla doldurmayı anlattım. Golang'da bu (birden) Uygun ve açık olduğu ortaya çıktı.

  • Burada “Düşmeyi” minimuma indirmeyi planlıyorum. Daha esnek işleme lehine (bunu aşağıda göstereceğim).

3. Pencere sınıflarını içeren tablo

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

Tablonun satırları, adı ile birlikte çift köşeli parantez içindedir. En başından beri daha kolay olamazdı. O anda etkin olan pencereye bağlı olarak aşağıdaki seçenekleri seçebilirsiniz:

  • Kendi “kısayol tuşları” setiniz “Eylemler =…”. Değilse/boşsa hiçbir şey yapmayın.
  • "MouseClickDrops" anahtarını değiştirin - bir fare tıklaması algılandığında ne yapılmalı. Xswitcher'ın açıldığı noktada “nereye tıkladıkları” ile ilgili bir detay olmadığından tamponu varsayılan olarak sıfırlıyoruz. Ancak terminallerde (örneğin) bunu yapmak zorunda değilsiniz (genellikle).

4. Bir (veya daha fazla) tıklama dizisi, bir veya diğer kancayı tetikler

# 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 iki tipe ayrılır. Yerleşik, açıklayıcı adlara sahip (NewWord, NewSentence, Compose) ve programlanabilir.

Programlanabilir adlar “Eylem” ile başlar. Çünkü TOML v1.4, noktalı adlar tırnak içinde olmalıdır.

Her bölüm aşağıda açıklanmalıdır aynı isimde.

“Çıplak” müdavimlerle insanların aklını başından almamak için (deneyimlere göre, onların yazmakbelki on kişiden biri профессионалов), hemen ek sözdizimi uygularım.

  • "KAPALI:" (veya "AÇIK:") regexp'den önce (normal ifade), aşağıdaki düğmelerin serbest bırakılmasını (veya basılmasını) gerektirir.
    Daha sonra “haksız” bir düzenli ifade yapacağım. "|" boruları arasındaki parçaların ayrı ayrı kontrol edilmesiyle. "[LR]_SHIFT" gibi kayıtların sayısını azaltmak için (bunun açıkça gerekli olmadığı durumlarda).
  • "SIR:" Önceki koşul karşılanırsa (veya yoksa), o zaman "normal" bir düzenli ifadeyi kontrol ederiz. Ayrıntılar için hemen ^W'ye “regexp” kütüphanesini gönderiyorum. Çünkü hâlâ favori pcre'mle ("Perl uyumlu") uyumluluk derecesini öğrenme zahmetine girmedim.
  • İfade şeklinde yazılır "BUTTON_1: KOD1, BUTTON_2: KOD2" vb. tarama kodlarının alınma sırasına göre.
  • Kontrol her zaman sıranın sonuna "sıkıştırılır"yani kuyruğa “$” eklemenize gerek yok.
  • Bir satırdaki tüm kontroller birbiri ardına gerçekleştirilir ve “ben” ile birleştirilir. Ancak değer dizi olarak tanımlandığı için virgülden sonra alternatif bir çek yazabilirsiniz. Herhangi bir nedenle buna ihtiyaç duyulursa.
  • Değer "Sıra Uzunluğu = 8" tüm kontrollerin gerçekleştirildiği arabelleğin boyutunu sınırlar. Çünkü Hayatımda (şimdiye kadar) sonsuz kaynaklarla hiç karşılaşmadım.

5. Önceki bölümde açıklanan kancaların ayarlanması

# 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 asıl önemli olan "Eylem = [Dizi]". Önceki bölüme benzer şekilde, sınırlı sayıda yerleşik eylem vardır. Ve kenetlenme olasılığı prensipte sınırlı değildir (“Action.XXX” yazın ve bunun için başka bir bölüm yazamayacak kadar tembel olmayın).
Özellikle, bir kelimenin düzeltilmiş düzende yeniden yazılması iki bölüme ayrılmıştır: “düzeni orada belirtildiği şekilde değiştirin” и “yeniden yazın” (“RetypeWord”).

Kalan parametreler “sözlüğe” yazılır (golang'da "harita") belirli bir eylem için listeleri "Eylem"de yazılanlara bağlıdır.

Bir yığında birkaç farklı eylem tanımlanabilir (bölümler). Veya onu parçalara ayırabilirsiniz. Yukarıda gösterdiğim gibi.

Harici betiği yürütmek için hemen “Exec” eylemini ayarladım. Kaydedilen arabelleği stdin'e aktarma seçeneği ile.

  • “Bekle = 1” — çalışan işlemin tamamlanmasını bekleyin.
  • Muhtemelen, "yığınla" çevreye ek insanlar koymak isteyeceksiniz. ele geçirildiği pencere sınıfının adı gibi bilgiler.
    “İşleyicinizi bağlamak istiyor musunuz? Gitmeniz gereken yer burası."

Phew (nefes verilir). Hiçbir şeyi unutmamışım gibi görünüyor.

Hata! Evet unutmadım...
Başlatma yapılandırması nerede? Sabit kodda mı? Bunun gibi:

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

Nerede unuttum/hata yaptım? (bu olmadan olmaz)Umarım dikkatli okuyucular burunlarını sokamayacak kadar tembel olmazlar.

İyi şanslar!

Kaynak: habr.com

Yorum ekle