Linux์šฉ Xswitcher ๋ ˆ์ด์•„์›ƒ ๊ต์ •๊ธฐ: XNUMX๋‹จ๊ณ„

์œผ๋กœ ์ด์ „ ์ถœํŒ๋ฌผ ("๊ฐœ๋… ์ฆ๋ช…" ๋‹จ๊ณ„์˜ xswitcher)๋Š” ๊ฝค ๋งŽ์€ ๊ฑด์„ค์ ์ธ ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค. (์ข‹๋„ค์š”), ๋‚˜๋Š” ๊ณ„์†ํ•ด์„œ ์ž์œ  ์‹œ๊ฐ„์„ ํ”„๋กœ์ ํŠธ ๊ฐœ๋ฐœ์— ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ๋‚˜๋Š” ๋‹น์‹ ์˜ ์ž‘์€ ๊ฒƒ์„ ์“ฐ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค ... ๋‘ ๋ฒˆ์งธ ๋‹จ๊ณ„๋Š” ์ผ๋ฐ˜์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ตฌ์„ฑ ์„ค๊ณ„ ์ œ์•ˆ/ํ† ๋ก ์ž…๋‹ˆ๋‹ค.

Linux์šฉ Xswitcher ๋ ˆ์ด์•„์›ƒ ๊ต์ •๊ธฐ: XNUMX๋‹จ๊ณ„

์–ด์จŒ๋“  ์ผ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋จธ๋Š” ์ด๋Ÿฌํ•œ ๋ชจ๋“  ์ปจํŠธ๋กค์„ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ์—„์ฒญ๋‚˜๊ฒŒ ์ง€๋ฃจํ•˜๋‹ค๋Š” ๊ฒƒ์ด ๋ฐํ˜€์กŒ์Šต๋‹ˆ๋‹ค.

๊ทผ๊ฑฐ๊ฐ€ ์—†๋Š” ์ผ์ด ์—†๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด ๋‚ด๊ฐ€ ๋‹ค๋ฃจ๊ณ  ์žˆ๋Š” ๋‚ด์šฉ์˜ ์˜ˆ๊ฐ€ ๋‚ด๋ถ€์— ์žˆ์Šต๋‹ˆ๋‹ค.
์ „๋ฐ˜์ ์œผ๋กœ ํ›Œ๋ฅญํ•˜๊ฒŒ ๊ณ ์•ˆ๋œ(์ž˜ ๊ตฌํ˜„๋œ) Apache Kafka ๋ฐ ZooKeeper์ž…๋‹ˆ๋‹ค.
- ๊ตฌ์„ฑ? ํ•˜์ง€๋งŒ ์ง€๋ฃจํ•ด์š”! ๋ฉ์ฒญํ•œ XML("์ฆ‰์‹œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ"ํ•˜๊ธฐ ๋•Œ๋ฌธ)
- ์•„, ACL๋„ ์›ํ•˜์‹œ๋‚˜์š”? ํ•˜์ง€๋งŒ ๋„ˆ๋ฌด ์ง€๋ฃจํ•ด์š”! ํƒญ๋ธ”๋กœํผ... ๊ทธ๋Ÿฐ ๊ฑฐ์š”.

ํ•˜์ง€๋งŒ ๋‚ด ์ž‘์—…์—์„œ๋Š” ์ •๋ฐ˜๋Œ€๋‹ค. ์˜ค๋ฅธ์ชฝ (์•„์•„, ๊ฑฐ์˜ ์ฒ˜์Œ์ด ์•„๋‹™๋‹ˆ๋‹ค) ๊ตฌ์ถ•๋œ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋” ์‰ฝ๊ณ  ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊ณ„์†ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๊ฑฐ์˜) ๋‹ค์ด์–ด๊ทธ๋žจ์„ ์กฐ๋ฆฝํ•ฉ๋‹ˆ๋‹ค.

์ตœ๊ทผ ๋ฐ์ดํ„ฐ ๊ณผํ•™์ž๋“ค์˜ ๋…ธ๊ณ ์— ๊ด€ํ•œ Habrรฉ์˜ ๊ธฐ์‚ฌ๋ฅผ ์ ‘ํ–ˆ์Šต๋‹ˆ๋‹ค...
์ด ์ˆœ๊ฐ„์ด ๊ทธ๋“ค์—๊ฒŒ ์™„์ „ํžˆ ์‹คํ˜„๋˜์—ˆ๋‹ค๋Š” ๊ฒƒ์ด ๋ฐํ˜€์กŒ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ๋กœ๋Š” "๋ผ์ดํŠธ ๋ฒ„์ „"์ด๋ผ๊ณ  ๋งํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์ค‘ ๋ณผ๋ฅจ ๋ชจ๋ธ, OOP๋ฅผ ๊ฐ–์ถ˜ ๋…ธ๋ จํ•œ ํ”„๋กœ๊ทธ๋ž˜๋จธ ๋“ฑ โ€” ์ด ๋ชจ๋“  ๊ฒƒ์€ ๋‚˜์ค‘์— ์ด๋ฅ™ํ•  ๋•Œ ๋‚˜ํƒ€๋‚  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋””์ž์ด๋„ˆ๋Š” ์ง€๊ธˆ ์—ฌ๊ธฐ ์–ด๋”˜๊ฐ€์—์„œ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์š”์ ์„ ํŒŒ์•…ํ•˜์„ธ์š”. TOML์„ ๊ตฌ๋ฌธ ๊ธฐ๋ฐ˜์œผ๋กœ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ์‹œ๋ฏผ์—๊ฒŒ์„œ.

๊ทธ๋Š” ๋•Œ๋ฌธ์— (TOML) ํ•œํŽธ์œผ๋กœ๋Š” ์‚ฌ๋žŒ์ด ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด์— XML, JSON, YAML๊ณผ ๊ฐ™์€ ๋ณด๋‹ค ์ผ๋ฐ˜์ ์ธ ๊ตฌ๋ฌธ์œผ๋กœ 1:1๋กœ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค.
๋”์šฑ์ด, ์ œ๊ฐ€ "github.com/BurntSushi/toml"์—์„œ ์‚ฌ์šฉํ•œ ๊ตฌํ˜„์€ ๊ฐ€์žฅ ์œ ํ–‰ํ•˜์ง€๋Š” ์•Š์ง€๋งŒ(์—ฌ์ „ํžˆ 1.4 ๊ตฌ๋ฌธ) ๋™์ผํ•œ("๋‚ด์žฅ") JSON๊ณผ ๊ตฌ๋ฌธ์ ์œผ๋กœ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค.

์ฆ‰, ์›ํ•  ๊ฒฝ์šฐ ๊ฐ„๋‹จํžˆ "๋‹น์‹ ์˜ TOML๋กœ ์ˆฒ์„ ํ†ต๊ณผํ•˜์„ธ์š”. ์ €๋Š” XXX๋ฅผ ์›ํ•ฉ๋‹ˆ๋‹ค"๋ผ๊ณ  ๋งํ•˜๊ณ  ๋‹จ ํ•œ ์ค„๋กœ ์ฝ”๋“œ๋ฅผ "ํŒจ์น˜"ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ xswitcher๋ฅผ ๊ตฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ์ผ๋ถ€ ์ฐฝ์„ ์ž‘์„ฑํ•˜๋ ค๋Š” ๊ฒฝ์šฐ (์ž˜ ๋ชจ๋ฅด๊ฒ ์Œ) "์ด ๋นŒ์–ด๋จน์„ ๊ตฌ์„ฑ์œผ๋กœ" ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์„ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค.

๋‹ค๋ฅธ ๋ชจ๋“  ๊ฒฝ์šฐ ๊ตฌ๋ฌธ์€ "ํ‚ค = ๊ฐ’"์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. (๋ง ๊ทธ๋Œ€๋กœ = [some, that, array]์™€ ๊ฐ™์€ ๋ช‡ ๊ฐ€์ง€ ๋” ๋ณต์žกํ•œ ์˜ต์…˜) ๋‚˜๋Š” ์ƒ๊ฐ
์ง๊ด€์ ์œผ๋กœ ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
ํฅ๋ฏธ๋กœ์šด ์ ์€ "ํƒ„" ๊ฐ™์€ ์‹œ๊ธฐ(2013๋…„๊ฒฝ). ๋‹ค๋งŒ ์ €์™€ ๋‹ฌ๋ฆฌ TOML์˜ ์ €์ž๋Š” ์ ๋‹นํ•œ ๊ทœ๋ชจ๋กœ ๋“ค์–ด๊ฐ”์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์ด์ œ ๊ตฌํ˜„์„ ๋‚˜ ์ž์‹ ์—๊ฒŒ ๋งž๊ฒŒ ์กฐ์ •ํ•˜๋Š” ๊ฒƒ์ด ๋” ์‰ฌ์›Œ์กŒ์œผ๋ฉฐ ๊ทธ ๋ฐ˜๋Œ€์˜ ๊ฒฝ์šฐ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ ์šฐ๋ฆฌ๋Š” TOML์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค(์ด์ „ Windows INI์™€ ๋งค์šฐ ์œ ์‚ฌ). ๊ทธ๋ฆฌ๊ณ  ํ‚ค๋ณด๋“œ์˜ ์ตœ์‹  ์Šค์บ” ์ฝ”๋“œ ์„ธํŠธ์— ๋”ฐ๋ผ ์ผ๋ จ์˜ ํ›„ํฌ๋ฅผ ๋ถ€์ฐฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•˜๋Š” ๊ตฌ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์—๋Š” ์ง€๊ธˆ๊นŒ์ง€ ์ผ์–ด๋‚œ ์ผ์ด ํ•˜๋‚˜์”ฉ ๋‚˜์™€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‚ด๊ฐ€ ์™œ ์ด๋Ÿฐ ๊ฒฐ์ •์„ ํ–ˆ๋Š”์ง€์— ๋Œ€ํ•œ ์„ค๋ช….

0. ๊ธฐ๋ณธ ์ถ”์ƒํ™”

  • ์Šค์บ” ์ฝ”๋“œ ์ง€์ •. ๋‹จ์ˆœํžˆ ๋””์ง€ํ„ธ ์ฝ”๋“œ๋Š” ์‚ฌ๋žŒ์ด ์ฝ์„ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ํ™•์‹คํžˆ ๋ญ”๊ฐ€ ์กฐ์น˜๋ฅผ ์ทจํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋กค๋กœ์Šค์œ„์น˜).
    ๋‚˜๋Š” "golang-evdev"์—์„œ "ecodes.go"๋ฅผ ํ”๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. (์ €์ž๋Š” ๊ฝค ๋ฌธํ™”์ ์œผ๋กœ ํ‘œ์‹œํ–ˆ์ง€๋งŒ ์›๋ณธ ์†Œ์Šค๋ฅผ ๋ณด๊ธฐ์—๋Š” ๋„ˆ๋ฌด ๊ฒŒ์œผ๋ฅธ ์ƒํƒœ์˜€์Šต๋‹ˆ๋‹ค.) ๊ฝค ๋‘๋ ค์› ๋˜ ๋ถ€๋ถ„์„ (์ง€๊ธˆ์€) ์กฐ๊ธˆ ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. โ€œ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)์ด ๋‚ด์žฅ๋˜์–ด ์žˆ์œผ๋ฉฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„์€ "Action"์œผ๋กœ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด TOML v1.4, ์ ์ด ์žˆ๋Š” ์ด๋ฆ„์€ ๋”ฐ์˜ดํ‘œ๋กœ ๋ฌถ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ฐ ์„น์…˜์€ ์•„๋ž˜์— ์„ค๋ช…๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ™์€ ์ด๋ฆ„์œผ๋กœ.

"๋ฒŒ๊ฑฐ๋ฒ—์€" ๋‹จ๊ณจ ์†๋‹˜์œผ๋กœ ์‚ฌ๋žŒ๋“ค์˜ ๋งˆ์Œ์„ ์‚ฌ๋กœ์žก์ง€ ์•Š๊ธฐ ์œ„ํ•ด(๊ฒฝํ—˜์ƒ, ๊ทธ๋“ค์˜ ์“ฐ๊ธฐ์•„๋งˆ๋„ ์—ด์— ํ•˜๋‚˜์ฏค์€ ์ „๋ฌธ๊ฐ€), ์ฆ‰์‹œ ์ถ”๊ฐ€ ๊ตฌ๋ฌธ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

  • "๋„๊ธฐ:"(๋˜๋Š” "์ผœ๊ธฐ:") ์ •๊ทœ์‹(์ •๊ทœ์‹)์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋ฒ„ํŠผ์„ ๋–ผ๊ฑฐ๋‚˜ ๋ˆŒ๋Ÿฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    ๋‹ค์Œ์œผ๋กœ "๋ถˆ๊ณตํ‰ํ•œ" ์ •๊ทœ์‹์„ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ํŒŒ์ดํ”„ "|" ์‚ฌ์ด์˜ ์กฐ๊ฐ์„ ๋ณ„๋„๋กœ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค. "[LR]_SHIFT"์™€ ๊ฐ™์€ ๋ ˆ์ฝ”๋“œ ์ˆ˜๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด(๋ถ„๋ช…ํžˆ ํ•„์š”ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ)
  • "์ˆœ์„œ:" ์ด์ „ ์กฐ๊ฑด์ด ์ถฉ์กฑ๋˜๊ฑฐ๋‚˜ ์—†์œผ๋ฉด "์ •์ƒ์ ์ธ" ์ •๊ทœ์‹์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์„ ๋ณด๋ ค๋ฉด ์ฆ‰์‹œ "regexp" ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ^W๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๋‚˜๋Š” ๋‚ด๊ฐ€ ๊ฐ€์žฅ ์ข‹์•„ํ•˜๋Š” pcre(โ€œperl ํ˜ธํ™˜โ€)์™€์˜ ํ˜ธํ™˜์„ฑ ์ •๋„๋ฅผ ์•„์ง๊นŒ์ง€ ์•Œ์•„๋‚ด๋ ค๊ณ  ์• ์“ฐ์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • ํ‘œํ˜„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•์‹์œผ๋กœ ์ž‘์„ฑ๋ฉ๋‹ˆ๋‹ค. "BUTTON_1: CODE1, BUTTON_2: CODE2" ๋“ฑ, ์Šค์บ” ์ฝ”๋“œ๊ฐ€ ์ˆ˜์‹ ๋˜๋Š” ์ˆœ์„œ๋Œ€๋กœ.
  • ์ˆ˜ํ‘œ๋Š” ํ•ญ์ƒ ์‹œํ€€์Šค์˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์— "๋ฐ€์ฐฉ"๋ฉ๋‹ˆ๋‹ค.์ด๋ฏ€๋กœ ๊ผฌ๋ฆฌ์— "$"๋ฅผ ์ถ”๊ฐ€ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
  • ํ•œ ์ค„์˜ ๋ชจ๋“  ๊ฒ€์‚ฌ๋Š” ์ฐจ๋ก€๋กœ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค. "๋‚˜"๋กœ ๊ฒฐํ•ฉ๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ฐ’์ด ๋ฐฐ์—ด๋กœ ์„ค๋ช…๋˜๋ฏ€๋กœ ์‰ผํ‘œ ๋’ค์— ๋Œ€์ฒด ๊ฒ€์‚ฌ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์–ด๋–ค ์ด์œ ๋กœ๋“  ์ด๊ฒƒ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ.
  • ๊ฐ€์น˜ "์ˆœ์„œ ๊ธธ์ด = 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โ€๋ผ๊ณ  ์“ฐ๊ณ  ๋‹ค๋ฅธ ์„น์…˜์„ ์ž‘์„ฑํ•˜๋Š” ๋ฐ ๋„ˆ๋ฌด ๊ฒŒ์œผ๋ฅด์ง€ ๋งˆ์‹ญ์‹œ์˜ค).
ํŠนํžˆ ์ˆ˜์ •๋œ ๋ ˆ์ด์•„์›ƒ์—์„œ ๋‹จ์–ด๋ฅผ ๋‹ค์‹œ ์ž…๋ ฅํ•˜๋Š” ์ž‘์—…์€ ๋‘ ๋ถ€๋ถ„์œผ๋กœ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค. "๊ฑฐ๊ธฐ์— ์ง€์ •๋œ ๋Œ€๋กœ ๋ ˆ์ด์•„์›ƒ์„ ๋ณ€๊ฒฝํ•˜์„ธ์š”" ะธ โ€œ๋‹ค์‹œ ์ž…๋ ฅโ€(โ€œRetypeWordโ€).

๋‚˜๋จธ์ง€ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” "์‚ฌ์ „"์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค. (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.

๋‚ด๊ฐ€ ์–ด๋””์„œ ์žŠ์–ด๋ฒ„๋ ธ๊ฑฐ๋‚˜ ์‹ค์ˆ˜๋ฅผ ํ–ˆ๋‚˜์š”? (์ด๊ฑฐ ์—†์œผ๋ฉด ์•ˆ๋ผ), ์„ธ์‹ฌํ•œ ๋…์ž๋“ค์ด ์ฝ”๋ฅผ ์ฐŒ๋ฅผ ์ •๋„๋กœ ๊ฒŒ์œผ๋ฅด์ง€ ์•Š๊ธฐ๋ฅผ ์ง„์‹ฌ์œผ๋กœ ๋ฐ”๋ž๋‹ˆ๋‹ค.

ํ–‰์šด์„ ๋น•๋‹ˆ๋‹ค!

์ถœ์ฒ˜ : habr.com

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€