Trình sửa bố cục Xswitcher cho Linux: bước hai

Như ấn phẩm trước (xswitcher ở giai đoạn “chứng minh khái niệm”) đã nhận được khá nhiều phản hồi mang tính xây dựng (cái gì là tốt), Tôi tiếp tục dành thời gian rảnh của mình để phát triển dự án. Bây giờ tôi muốn dành một ít của bạn... Bước thứ hai sẽ không hoàn toàn bình thường: đề xuất/thảo luận về thiết kế cấu hình.

Trình sửa bố cục Xswitcher cho Linux: bước hai

Bằng cách nào đó, hóa ra các lập trình viên bình thường cảm thấy vô cùng nhàm chán khi thiết lập tất cả các điều khiển này.

Để không bị vô căn cứ, bên trong là một ví dụ về những gì tôi đang giải quyết.
Nhìn chung đã được hình thành một cách xuất sắc (và được triển khai tốt) Apache Kafka & ZooKeeper.
- Cấu hình? Nhưng nó thật nhàm chán! Xml ngu ngốc (vì nó đã "ngoài hộp").
- Ồ, bạn cũng muốn có ACL à? Nhưng chán quá! Tap-blooper... Đại loại như vậy.

Nhưng trong công việc của tôi thì hoàn toàn ngược lại. Phải (than ôi, hầu như không bao giờ là lần đầu tiên) mô hình được xây dựng cho phép bạn tiếp tục dễ dàng và tự nhiên hơn (Hầu hết) lắp ráp một sơ đồ.

Gần đây tôi tình cờ xem được một bài báo trên Habré về công việc khó khăn của các nhà khoa học dữ liệu...
Hóa ra khoảnh khắc này đã được hiện thực hóa trọn vẹn đối với họ. Và trong thực tế của tôi, như họ nói, "phiên bản nhẹ". Các mô hình nhiều tập, các lập trình viên dày dạn kinh nghiệm luôn sẵn sàng sử dụng OOP, v.v. — tất cả điều này sẽ xuất hiện sau khi/nếu nó cất cánh. Nhưng nhà thiết kế cần phải bắt đầu từ đâu đó ngay tại đây và ngay bây giờ.

Vào vấn đề. Tôi lấy TOML làm cơ sở cú pháp từ công dân này.

Bởi vì anh ấy (TOML) một mặt, con người có thể chỉnh sửa được. Mặt khác, nó được dịch 1:1 sang bất kỳ cú pháp phổ biến nào: XML, JSON, YAML.
Hơn nữa, cách triển khai mà tôi đã sử dụng từ “github.com/BurntSushi/toml”, mặc dù không phải là cách triển khai hợp thời nhất (vẫn là cú pháp 1.4), nhưng lại tương thích về mặt cú pháp với cùng một JSON (“tích hợp sẵn”).

Nghĩa là, nếu muốn, bạn có thể chỉ cần nói “đi xuyên rừng với TOML đó của bạn, tôi muốn XXX” và “vá” mã chỉ bằng một dòng.

Vì vậy, nếu bạn muốn viết một số cửa sổ để cấu hình xswitcher (Tôi không chắc) Sẽ không có vấn đề gì xảy ra “với cấu hình chết tiệt này của bạn.”

Đối với tất cả những thứ khác, cú pháp dựa trên “key = value” (và theo nghĩa đen là một vài tùy chọn phức tạp hơn, như = [some, that, array]) tôi giả sử
trực quan thuận tiện.
Điều thú vị là "cháy" cùng thời điểm (khoảng năm 2013). Chỉ có điều, không giống như tôi, tác giả của TOML đã đưa ra một quy mô phù hợp.

Vì vậy, giờ đây tôi dễ dàng điều chỉnh việc triển khai nó cho phù hợp với bản thân mình hơn chứ không phải ngược lại.

Nói chung, chúng tôi lấy TOML (rất giống với INI của Windows cũ). Và chúng tôi có một cấu hình trong đó chúng tôi mô tả cách gắn một loạt móc tùy thuộc vào bộ mã quét mới nhất từ ​​bàn phím. Dưới đây, từng phần một, là những gì đã xảy ra cho đến nay. Và lời giải thích tại sao tôi lại quyết định như vậy.

0. trừu tượng cơ bản

  • Quét mã chỉ định. Chắc chắn cần phải làm gì đó về vấn đề này, vì đơn giản là các mã kỹ thuật số hoàn toàn không thể đọc được con người (chỉ có tôi mới loloswitcher).
    Tôi đã loại bỏ “ecodes.go” khỏi “golang-evdev” (Tôi quá lười xem nguồn gốc, mặc dù tác giả đã chỉ ra nó khá văn hóa). Tôi đã sửa lại một chút (hiện tại) điều gì đó khá đáng sợ. Giống như “LEFTBRACE” → “L_BRACE”.
  • Ngoài ra, ông còn đưa ra khái niệm “khóa trạng thái”. Vì ngữ pháp thông thường được sử dụng không cho phép viết những đoạn văn dài. (Nhưng nó cho phép bạn kiểm tra với chi phí tối thiểu. Nếu bạn chỉ sử dụng tính năng ghi “trực tiếp”.)
  • Sẽ có một “bộ khử trùng lặp” tích hợp sẵn cho những gì được nhấn. Do đó, trạng thái "lặp lại"=2 sẽ được ghi một thời gian.

1. Phần Mẫu

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

Một từ trong ngôn ngữ của con người có ký hiệu ngữ âm bao gồm những gì? (hoặc là vấn đề của đồ thị hay còn gọi là “chữ tượng hình”)? Một số loại "tấm" khủng khiếp. Vì vậy, tôi đưa ra ngay khái niệm “mẫu”.

2. Phải làm gì khi nhấp vào thứ gì đó (mã quét khác đã đến)

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

Tổng cộng có 768 mã. (Nhưng “để đề phòng”, tôi đã chèn những điều “bất ngờ” vào mã xswitcher).
Bên trong, tôi đã mô tả việc điền vào mảng các liên kết đến các hàm “phải làm gì”. Ở golang đây là (đột nhiên) Hóa ra là thuận tiện và rõ ràng.

  • Tôi định giảm mức độ “Rớt” xuống mức tối thiểu ở nơi này. Ủng hộ việc xử lý linh hoạt hơn (tôi sẽ trình bày bên dưới).

3. Bảng có các lớp cửa sổ

# 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ác hàng của bảng nằm trong dấu ngoặc vuông kép với tên của nó. Nó không thể dễ dàng hơn ngay lập tức. Tùy thuộc vào cửa sổ hiện đang hoạt động, bạn có thể chọn các tùy chọn sau:

  • Bộ “phím nóng” “Hành động =…” của riêng bạn. Nếu không/trống, không làm gì cả.
  • Chuyển đổi “MouseClickDrops” - phải làm gì khi phát hiện một cú nhấp chuột. Vì tại thời điểm xswitcher được bật, không có thông tin chi tiết nào về “nơi họ nhấp vào”, nên chúng tôi đặt lại bộ đệm theo mặc định. Nhưng trong thiết bị đầu cuối (ví dụ), bạn không phải làm điều này (thường xuyên).

4. Một (hoặc một số) chuỗi nhấp chuột sẽ kích hoạt một hoặc một móc nối khác

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

Móc được chia thành hai loại. Tích hợp sẵn với các tên dễ hiểu (NewWord, NewSentence, Compose) và có thể lập trình được.

Tên có thể lập trình bắt đầu bằng “Hành động”. Bởi vì TOML v1.4, tên có dấu chấm phải để trong dấu ngoặc kép.

Mỗi phần nên được mô tả dưới đây có cùng tên.

Để không làm mọi người choáng váng với những khách quen “trần trụi” (theo kinh nghiệm, viếtcó lẽ một trong mười các chuyên gia), tôi thực hiện ngay cú pháp bổ sung.

  • "TẮT:" (hoặc "BẬT:") trước regrec (biểu thức chính quy) yêu cầu các nút sau được nhả (hoặc nhấn).
    Tiếp theo tôi sẽ tạo một biểu thức chính quy “không công bằng”. Với việc kiểm tra riêng biệt các chi tiết giữa các ống "|". Để giảm số lượng bản ghi như "[LR]_SHIFT" (điều này rõ ràng là không cần thiết).
  • "SEQ:" Nếu điều kiện trước đó được đáp ứng (hoặc không có), thì chúng tôi sẽ kiểm tra biểu thức chính quy "bình thường". Để biết chi tiết, tôi gửi ngay tới ^W thư viện “regexp”. Bởi vì tôi vẫn chưa buồn tìm hiểu mức độ tương thích với pcre yêu thích của mình (“tương thích với Perl”).
  • Biểu thức được viết dưới dạng "BUTTON_1: CODE1, BUTTON_2: CODE2" v.v., theo thứ tự nhận được mã quét.
  • Séc luôn được “gắn” vào cuối chuỗi, do đó không cần thêm “$” vào đuôi.
  • Tất cả các kiểm tra trên một dòng được thực hiện lần lượt và được kết hợp bởi “tôi”. Nhưng vì giá trị được mô tả dưới dạng mảng nên bạn có thể viết dấu kiểm thay thế sau dấu phẩy. Nếu điều này là cần thiết vì lý do nào đó.
  • Giá trị "Độ dài tuần tự = 8" giới hạn kích thước của bộ đệm mà tất cả các kiểm tra được thực hiện. Bởi vì Tôi (cho đến bây giờ) chưa bao giờ gặp phải nguồn tài nguyên vô tận trong đời.

5. Đặt móc được mô tả ở phần trước

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

Điều chính ở đây là "Hành động = [Mảng]". Tương tự như phần trước, có một số hành động tích hợp hạn chế. Và về nguyên tắc, khả năng lắp ghép không bị giới hạn (viết “Action.XXX” và đừng lười viết phần khác cho nó nhé).
Cụ thể, việc gõ lại một từ trong bố cục đã sửa được chia làm XNUMX phần: “thay đổi bố cục theo quy định ở đó” и “gõ lại” (“Gõ lại từ”).

Các thông số còn lại được ghi vào “dictionary” ("bản đồ" trong golang) đối với một hành động nhất định, danh sách của họ phụ thuộc vào nội dung được viết trong “Hành động”.

Một số hành động khác nhau có thể được mô tả trong một đống (phần). Hoặc bạn có thể kéo nó ra. Như tôi đã trình bày ở trên.

Tôi ngay lập tức thiết lập hành động “Exec” để thực thi tập lệnh bên ngoài. Với tùy chọn đẩy bộ đệm đã ghi vào stdin.

  • “Chờ = 1” - đợi quá trình chạy hoàn tất.
  • Có thể, “đến đống” bạn sẽ muốn đưa thêm người vào môi trường. thông tin như tên của lớp cửa sổ mà nó bị chặn.
    “Bạn có muốn kết nối trình xử lý của mình không? Đây là nơi bạn cần phải đi.”

Phù (thở dài). Có vẻ như tôi chưa quên điều gì cả.

Ối! Ừ, tôi không quên...
Cấu hình khởi chạy ở đâu? Trong mã cứng? Như thế:

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

Tôi đã quên/sai ở đâu? (không thể nào không có cái này), Tôi thực sự hy vọng rằng những độc giả chú ý sẽ không lười biếng chọc mũi.

Chúc may mắn!

Nguồn: www.habr.com

Thêm một lời nhận xét