ตัวแก้ไขเค้าโครง Xswitcher สำหรับ Linux: ขั้นตอนที่สอง

ในขณะที่ สิ่งพิมพ์ก่อนหน้า (xswitcher อยู่ในขั้น "พิสูจน์แนวคิด") ได้รับการตอบรับเชิงสร้างสรรค์ค่อนข้างมาก (ซึ่งเป็นสิ่งที่ดี)ฉันยังคงใช้เวลาว่างในการพัฒนาโครงการต่อไป ตอนนี้ฉันอยากจะใช้เวลาของคุณสักหน่อย... ขั้นตอนที่สองจะไม่ปกติ: ข้อเสนอ/การอภิปรายเกี่ยวกับการออกแบบการกำหนดค่า

ตัวแก้ไขเค้าโครง Xswitcher สำหรับ Linux: ขั้นตอนที่สอง

ปรากฎว่าโปรแกรมเมอร์ทั่วไปพบว่าการตั้งค่าการควบคุมเหล่านี้น่าเบื่ออย่างไม่น่าเชื่อ

เพื่อไม่ให้ไม่มีมูลความจริง ข้างในเป็นตัวอย่างของสิ่งที่ฉันกำลังเผชิญอยู่
โดยรวมแล้ว Apache Kafka & ZooKeeper มีความคิดที่ยอดเยี่ยม (และนำไปใช้ได้ดี)
- การกำหนดค่า? แต่มันน่าเบื่อ! Dumb xml (เพราะมัน "นอกกรอบ")
- โอ้ คุณต้องการ ACL ด้วยเหรอ? แต่มันน่าเบื่อมาก! Tap-blooper... อะไรประมาณนั้น

แต่ในงานของฉันมันตรงกันข้ามเลย ขวา (อนิจจาแทบไม่เคยเป็นครั้งแรก) แบบจำลองที่สร้างขึ้นช่วยให้คุณสามารถดำเนินการต่อได้อย่างง่ายดายและเป็นธรรมชาติ (เกือบ) ประกอบไดอะแกรม

ฉันเพิ่งเจอบทความเกี่ยวกับHabréเกี่ยวกับการทำงานหนักของนักวิทยาศาสตร์ข้อมูล...
ปรากฎว่าช่วงเวลานี้ได้รับการตระหนักรู้อย่างเต็มที่สำหรับพวกเขา และในทางปฏิบัติของฉันอย่างที่พวกเขาพูดว่า "เวอร์ชันเบา" โมเดลหลายวอลุ่ม โปรแกรมเมอร์ผู้ช่ำชองพร้อม OOP ที่พร้อมใช้งาน ฯลฯ — ทั้งหมดนี้จะปรากฏขึ้นในภายหลังเมื่อ/หากมันหายไป แต่นักออกแบบจำเป็นต้องเริ่มต้นที่นี่และตอนนี้

ไปถึงจุด. ฉันใช้ TOML เป็นพื้นฐานทางวากยสัมพันธ์ จากพลเมืองคนนี้.

เพราะว่าเขา (ทอมล์) ในด้านหนึ่ง มนุษย์สามารถแก้ไขได้ ในทางกลับกัน มันถูกแปล 1:1 เป็นรูปแบบทั่วไปใดๆ: XML, JSON, YAML
ยิ่งไปกว่านั้น การใช้งานที่ฉันใช้จาก “github.com/BurntSushi/toml” แม้ว่าจะไม่ใช่รูปแบบที่ทันสมัยที่สุด (ยังคงเป็นไวยากรณ์ 1.4) แต่ก็สามารถทำงานร่วมกันทางวากยสัมพันธ์กับ JSON เดียวกัน (“ในตัว”) ได้

นั่นคือหากคุณต้องการ คุณสามารถพูดว่า "ไปลุยป่าด้วย TOML ของคุณ ฉันต้องการ XXX" และ "แก้ไข" โค้ดด้วยบรรทัดเดียว

ดังนั้นหากคุณต้องการเขียนบางหน้าต่างเพื่อกำหนดค่า xswitcher (ฉันไม่แน่ใจ) คาดว่าจะไม่มีปัญหาใด ๆ “ด้วยการกำหนดค่าบ้า ๆ ของคุณ”

สำหรับรายการอื่นๆ ทั้งหมด ไวยากรณ์จะขึ้นอยู่กับ "key = value" (และตัวเลือกที่ซับซ้อนกว่าสองสามตัวเช่น = [some, that, array]) ฉันคิดว่า
สะดวกอย่างสังหรณ์ใจ
สิ่งที่น่าสนใจก็คือ "เผา" ในช่วงเวลาเดียวกัน (ประมาณปี 2013) มีเพียงผู้เขียน TOML เท่านั้นที่ต่างจากฉันตรงที่มีระดับที่เหมาะสม

ดังนั้น ตอนนี้มันง่ายกว่าสำหรับฉันที่จะปรับการใช้งานให้เหมาะกับตัวเอง และไม่ใช่ในทางกลับกัน

โดยทั่วไป เราใช้ TOML (คล้ายกับ Windows INI รุ่นเก่ามาก) และเรามีการกำหนดค่าซึ่งเราจะอธิบายวิธีติดชุดตะขอโดยขึ้นอยู่กับชุดรหัสสแกนล่าสุดจากแป้นพิมพ์ ด้านล่างนี้ทีละชิ้นคือสิ่งที่เกิดขึ้นจนถึงตอนนี้ และคำอธิบายว่าทำไมฉันถึงตัดสินใจแบบนี้

0. นามธรรมพื้นฐาน

  • สแกนการกำหนดรหัส จำเป็นต้องทำอะไรบางอย่างเกี่ยวกับเรื่องนี้อย่างแน่นอน เนื่องจากรหัสดิจิทัลไม่สามารถอ่านได้โดยมนุษย์อย่างแน่นอน (นั่นเป็นเพียงฉันเท่านั้น) โลโลสวิตช์เกอร์).
    ฉันสลัด "ecodes.go" ออกจาก "golang-evdev" (ฉันขี้เกียจเกินกว่าจะดูแหล่งที่มาดั้งเดิม แม้ว่าผู้เขียนจะระบุว่าค่อนข้างเป็นวัฒนธรรมก็ตาม) ฉันแก้ไขบางสิ่งที่ค่อนข้างน่ากลัว (สำหรับตอนนี้) เล็กน้อย เช่น “LEFTBRACE” → “L_BRACE”
  • นอกจากนี้ เขายังแนะนำแนวคิดของ "กุญแจสถานะ" เนื่องจากไวยากรณ์ปกติที่ใช้ไม่อนุญาตให้มีข้อความยาวๆ (แต่ช่วยให้คุณตรวจสอบได้โดยมีค่าใช้จ่ายน้อยที่สุด หากคุณใช้การบันทึกแบบ "โดยตรง")
  • จะมี "ตัวขจัดข้อมูลซ้ำซ้อน" ในตัวของสิ่งที่กด ดังนั้นสถานะ "repeat"=2 จะถูกเขียน หนึ่ง เวลา

1. ส่วนเทมเพลต

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

คำภาษามนุษย์ที่มีเครื่องหมายการออกเสียงประกอบด้วยอะไร? (ไม่ว่าจะเป็นเรื่องของกราฟหรือที่เรียกว่า "อักษรอียิปต์โบราณ")? "แผ่นงาน" ที่น่ากลัวบางอย่าง ดังนั้นฉันจึงแนะนำแนวคิดของ "เทมเพลต" ทันที

2. จะทำอย่างไรเมื่อมีการคลิกบางสิ่ง (รหัสสแกนอื่นมาถึงแล้ว)

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

มีทั้งหมด 768 รหัส (แต่ “เผื่อไว้” ฉันใส่คำว่า “เซอร์ไพรส์” ลงในโค้ด xswitcher)
ข้างในฉันอธิบายการเติมอาร์เรย์ด้วยลิงก์ไปยังฟังก์ชัน "สิ่งที่ต้องทำ" ในโกลังนี่คือ (กะทันหัน) ปรากฏว่าสะดวกและชัดเจน

  • ฉันวางแผนที่จะลด "การดรอป" ให้เหลือน้อยที่สุดในที่นี้ เพื่อสนับสนุนการประมวลผลที่ยืดหยุ่นมากขึ้น (ฉันจะแสดงด้านล่าง)

3. ตารางที่มีคลาสหน้าต่าง

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

แถวของตารางอยู่ในวงเล็บเหลี่ยมคู่พร้อมชื่อ มันไม่ง่ายไปกว่านี้อีกแล้ว คุณสามารถเลือกตัวเลือกต่อไปนี้ ทั้งนี้ขึ้นอยู่กับหน้าต่างที่ใช้งานอยู่ในปัจจุบัน:

  • ชุด "ปุ่มลัด" ของคุณเอง "การกระทำ = ..." ถ้าไม่/ว่างเปล่า ไม่ต้องทำอะไรเลย
  • สลับ "MouseClickDrops" - จะทำอย่างไรเมื่อตรวจพบการคลิกเมาส์ เนื่องจาก ณ จุดที่ xswitcher เปิดอยู่นั้นไม่มีรายละเอียดเกี่ยวกับ “ตำแหน่งที่พวกเขาคลิก” เราจึงรีเซ็ตบัฟเฟอร์ตามค่าเริ่มต้น แต่ในเทอร์มินัล (ตัวอย่าง) คุณไม่จำเป็นต้องทำเช่นนี้ (โดยปกติ).

4. การคลิกหนึ่งลำดับ (หรือหลายรายการ) จะกระตุ้นให้เกิดฮุกหนึ่งหรืออีกอันหนึ่ง

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

ตะขอแบ่งออกเป็นสองประเภท มีชื่อ "พูด" ในตัว (NewWord, NewSentence, Compose) และสามารถตั้งโปรแกรมได้

ชื่อที่ตั้งโปรแกรมได้จะขึ้นต้นด้วย “การกระทำ” เพราะ TOML v1.4 ชื่อที่มีจุดต้องอยู่ในเครื่องหมายคำพูด

ควรอธิบายแต่ละส่วนด้านล่าง ด้วยชื่อเดียวกัน.

เพื่อไม่ให้กระทบจิตใจผู้คนด้วยขาประจำที่ "เปลือยเปล่า" (จากประสบการณ์ของพวกเขา เขียนอาจจะเป็นหนึ่งในสิบ มืออาชีพ) ฉันจะใช้ไวยากรณ์เพิ่มเติมทันที

  • "ปิด:" (หรือ "เปิด:") ก่อน regexp (นิพจน์ปกติ) ต้องการให้ปล่อย (หรือกด) ปุ่มต่อไปนี้
    ต่อไป ฉันจะสร้างสำนวนปกติที่ "ไม่ยุติธรรม" โดยแยกการตรวจสอบชิ้นส่วนระหว่างท่อ "|" เพื่อลดจำนวนบันทึก เช่น "[LR]_SHIFT" (โดยที่ไม่จำเป็นอย่างชัดเจน)
  • "ซีคิว:" หากตรงตามเงื่อนไขก่อนหน้า (หรือขาดหายไป) เราจะตรวจสอบกับนิพจน์ทั่วไป "ปกติ" สำหรับรายละเอียด ฉันจะส่งไปที่ ^W ไลบรารี “regexp” ทันที เพราะฉันยังไม่ใส่ใจที่จะค้นหาระดับความเข้ากันได้กับ pcre ที่ฉันชื่นชอบ ("รองรับ perl")
  • การแสดงออกถูกเขียนในรูปแบบ "BUTTON_1: รหัส1, BUTTON_2: รหัส2" ฯลฯ ตามลำดับที่ได้รับรหัสสแกน
  • เช็คจะ "แนบ" ไว้ที่จุดสิ้นสุดของลำดับเสมอดังนั้นจึงไม่จำเป็นต้องเพิ่ม “$” ที่ส่วนท้าย
  • การตรวจสอบทั้งหมดในหนึ่งบรรทัดจะดำเนินการทีละรายการ และรวมด้วยคำว่า "ฉัน" แต่เนื่องจากค่านี้อธิบายเป็นอาร์เรย์ คุณจึงสามารถเขียนเช็คสำรองหลังเครื่องหมายจุลภาคได้ หากจำเป็นด้วยเหตุผลบางประการ
  • มูลค่า "ความยาวลำดับ = 8" จำกัดขนาดของบัฟเฟอร์ที่ใช้ในการตรวจสอบทั้งหมด เพราะ ฉัน (จนถึงขณะนี้) ไม่เคยพบกับทรัพยากรที่ไม่มีที่สิ้นสุดในชีวิตของฉัน

5. การตั้งตะขอตามที่อธิบายไว้ในส่วนก่อนหน้า

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

สิ่งสำคัญที่นี่คือ "การกระทำ = [อาร์เรย์]". เช่นเดียวกับส่วนก่อนหน้า มีชุดการดำเนินการในตัวที่จำกัด และความเป็นไปได้ในการเชื่อมต่อนั้นไม่ได้จำกัดอยู่ในหลักการ (เขียนว่า “Action.XXX” และอย่าขี้เกียจเกินไปที่จะเขียนส่วนอื่นลงไป).
โดยเฉพาะการพิมพ์คำซ้ำในรูปแบบที่แก้ไขจะแบ่งออกเป็นสองส่วน: “เปลี่ยนเค้าโครงตามที่ระบุไว้ที่นั่น” и “พิมพ์ซ้ำ” (“พิมพ์คำใหม่”).

พารามิเตอร์ที่เหลือจะถูกเขียนลงใน "พจนานุกรม" ("แผนที่" ใน golang) สำหรับการกระทำที่กำหนด รายการจะขึ้นอยู่กับสิ่งที่เขียนไว้ใน "การกระทำ"

การดำเนินการต่างๆ มากมายสามารถอธิบายได้ในฮีปเดียว (ส่วน). หรือจะดึงออกจากกันก็ได้ ดังที่ผมแสดงไว้ข้างต้น

ฉันตั้งค่าการดำเนินการ “Exec” ทันทีเพื่อเรียกใช้สคริปต์ภายนอก พร้อมตัวเลือกในการดันบัฟเฟอร์ที่บันทึกไว้ไปยัง stdin

  • “รอ = 1” — รอให้กระบวนการทำงานเสร็จสิ้น
  • อาจเป็นไปได้ว่า "ในกอง" คุณจะต้องการมีคนเพิ่มเติมในสภาพแวดล้อม ข้อมูลเช่นชื่อของคลาสหน้าต่างที่ถูกดักจับ
    “คุณต้องการเชื่อมต่อผู้ดูแลของคุณหรือไม่? นี่คือที่ที่คุณต้องไป”

วุ้ย (หายใจออก) ดูเหมือนว่าฉันจะไม่ลืมอะไรเลย

อ๊ะ! ใช่ ฉันไม่ลืม...
การกำหนดค่าการเปิดตัวอยู่ที่ไหน? ในฮาร์ดโค้ด? เช่นนั้น:

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

ฉันลืม/ทำผิดตรงไหน? (ไม่มีทางถ้าไม่มีสิ่งนี้)ฉันหวังเป็นอย่างยิ่งว่าผู้อ่านที่เอาใจใส่จะไม่ขี้เกียจเกินไปที่จะแหย่จมูก

โชคดี!

ที่มา: will.com

เพิ่มความคิดเห็น