借evdev之力 Linux全域性熱鍵魔改造

2021-09-16 12:38:16 字數 4803 閱讀 5681

霓虹語標題我都想好了。evdevの力を貸して、linuxでホットキーの魔改造

linux使用者就像minecraft玩家,雖然大家玩的都是minecraft,但是,臥槽,我們一定是在玩不同的遊戲(見到建築師的mc作品時來自小白的驚嘆)。要讓自己的linux別人不會用,別人的linux自己不會用,最重要的當然是要把快捷鍵改得驚天地泣鬼神。

作為乙個vim癮君子,我的需求就是手盡量不要離開主鍵盤區。對,方向鍵我都不想按。於是我想通過一些組合鍵去實現上下左右。有人認為capslock按起來方便,我自己比較喜歡按alt,因為就在空格鍵旁邊,觸手可及。總的來說,我希望alt+[h|j|k|l]分別變成左下上右,alt+0變成home,alt+4變成end。

我自己試用過很多修改鍵位或者新增熱鍵的工具,包括著名的autokey。很可惜,它們大都不好用。比如說,我採取這樣的操作序列alt down, k down, k up, alt up,這些軟體大多會採取在檢測到k down的時候,同時發出alt up, ↑ down,以便撤銷掉先前乙個alt down的作用,然後發出鍵按下的事件。這裡有乙個問題,就是很多gui在alt down, alt up之後,會喚出選單,從而失焦於輸入框。

另外一方面就是,真的不是**都能用。至少,你不可能拿它去玩賽車遊戲。你也不能在tty中繼續使用這些熱鍵。此外還有許許多多的地方不能使用。熱鍵是一種會上癮的東西,在它失效的時候,你就會有戒斷症候群。想摔鍵盤。

我試過從gnome和x11入手,貌似沒有什麼好用的方案。xgrabkeyboard一定程度上可以做到接管的效果,但是要指定視窗,似乎還會讓視窗失焦。總而言之稍微有點太繞。不過linux最好的一點,就是它很裸露。如果從高於驅動低於x11的層入手,興許會有比較好的效果。

evdev核心中通用的輸入裝置驅動,它為裝置提供了/dev/input下字元裝置介面。它非常底層,核心在進行中斷處理後,第一時間就將輸入資料交由它處理。但是它一點都不反直覺,甚至還提供了很好用的工具libevdev,可以直接用python處理訊息。不少人改遊戲手柄都是通過evdev進行的。

uinput是乙個特殊的虛擬裝置,它允許你直接在使用者態向核心插入輸入事件 —— 一般而言就是直接向/dev/uinput寫資料,不過當然,要服從libevdev提供的介面/資料結構。這些事件之後會在另乙個evdev字元裝置裡,假裝是物理裝置的輸入,被x的libinput取出來或者由tty轉為stdin。

這種實驗性的東西就不用c寫了。evdev提供了十分好用的python bindings,我們可以直接在python裡寫我們的快捷鍵配置。我們先用pip install evdev安裝它,然後在用它提供的示例指令碼來測試一下evdev輸入的究竟是什麼東西:sudo python -m evdev.evtest /dev/input/by-path/platform-i8042-serio-0-event-kbd(這裡我鍵盤的路徑是i8042鍵盤控制器上的,你需要根據你電腦上的配置來調整)

time 1504189579.19    type 4 (ev_msc), code 4    (msc_scan), value 21

time 1504189579.19 type 1 (ev_key), code 21 (key_y), value 1

time 1504189579.19 --------- syn_report --------

time 1504189579.28 type 4 (ev_msc), code 4 (msc_scan), value 21

time 1504189579.28 type 1 (ev_key), code 21 (key_y), value 0

time 1504189579.28 --------- syn_report --------

time 1504189579.29 type 4 (ev_msc), code 4 (msc_scan), value 18

time 1504189579.29 type 1 (ev_key), code 18 (key_e), value 1

time 1504189579.29 --------- syn_report --------

time 1504189579.4 type 4 (ev_msc), code 4 (msc_scan), value 18

time 1504189579.4 type 1 (ev_key), code 18 (key_e), value 0

time 1504189579.4 --------- syn_report --------

time 1504189579.48 type 4 (ev_msc), code 4 (msc_scan), value 31

time 1504189579.48 type 1 (ev_key), code 31 (key_s), value 1

time 1504189579.48 --------- syn_report --------

time 1504189579.64 type 4 (ev_msc), code 4 (msc_scan), value 31

time 1504189579.64 type 1 (ev_key), code 31 (key_s), value 0

time 1504189579.64 --------- syn_report --------

這裡我按了yes三個鍵,可以看到,每乙個動作(按下或釋放,表現在ev_key的value的1或0上),都會產生三個事件,分別是ev_mscev_keyev_syn。根據 再談linux-input子系統/的說法,事實上是有四個訊息的發出,但是第乙個通常不被支援(隱身了),第二個msc_scan通常會被應用程式忽略,第三個ev_key才是真正會被接收的,第四個是同步,可以看到就是用來產生萌萌的分界線的(笑)

有了這一層認識我們就知道,三個事件合起來,才是一次真正的輸入。

就我的需求而言,我腦子裡第乙個浮現的抽象機制就是狀態機,不知為何。乙個比較合適的設計是兩層的自動機,第一層用來為這三個一組的事件分組,分好組之後成為第二層狀態機的輸入,乙個二元組(key-scan-code, up/down/hold)。第二層狀態機我花了好些時間去構思,結果大概是這樣的:

state

input pattern

transition

action

normal

(left alt, down)

alt-

normal

else

normal

inject

alt(j/k/h/l/0/4, *)

alt(left alt, up)

normal

inject_alt_down, inject_alt_up

altelse

inject

inject_alt_down, inject

inject

(j/k/h/l/0/4, *)

inject

(left alt, up)

normal

inject_alt_up

inject

else

inject

inject

(j/k/h/l/0/4, *)

(left alt, up)

normal

-else

inject

inject_alt_down, inject

是不是頭都暈了。把它畫出來或許會比較清楚,不過我也沒這個閒心拿繪圖軟體再畫一遍了。有四個狀態,分別是:

關於evdev本身的使用上,evdev的文件已經說得非常詳盡。在這個指令碼裡,僅僅用到了少量的功能,比如從/dev/input裡讀事件,我是block read,但是你也可以用select或者epoll去非同步完成這些操作。:

# 留意!需要root許可權!

dev = evdev.inputdevice('/dev/input/by-path/platform-i8042-serio-0-event-kbd')

for event in dev.read_loop():

kev = evdev.categorize(event)

ks.input(kev) # 第一層狀態機

process(ks) # 第二層狀態機,以第一層狀態的結果為輸入

inject這樣的操作就是往uinput裡面一次過寫三個事件(含同步):

ui = evdev.uinput()

def __inject(keycode, keystate):

global ui

ui.write(ecodes.ev_msc, ecodes.msc_scan, keycode)

ui.write(ecodes.ev_key, keycode, keystate)

ui.syn()

當然,最重要的一點是怎樣做到獨佔讀,也就是託管整個裝置的事件處理不讓它側漏呢?我們為它設定grab。grab了之後,整個系統只有當前程序讀得到鍵盤的輸入。如果這個裝置已經被別人grab了,這個操作就會失敗。

dev.grab()

dev.ungrab()

整份**已經上傳到 歡迎斧正。

借Windows說明Linux分割槽和掛載點

在介紹linux分割槽和掛載點前,我想先說乙個windows的例子,windows大家都比較熟,再借這個例子來說明什麼是linux分割槽和掛載點。1.消失了的分割槽 在winpe下,我將一塊硬碟分成乙個主分割槽和四個邏輯分割槽,主分割槽指派驅動器號c,四個邏輯分割槽均不指派驅動器號 這會出現什麼情況...

linux全域性頁目錄項

昨天昊哥 問全域性頁目錄項的問題,突然一聽搞不清楚。今早過來看了下書,順便總結下。參照 linux0.12核心 首先,linux記憶體分頁管理是通過頁目錄項和頁表項頁目錄項和記憶體頁表所組成的二級表進行。如圖13 1所示。頁目錄項和頁表項結構一致,表項結構也相同 見圖13 4 頁目錄表中每個表象 4...

linux 堆 棧 全域性變數存放

在學習 深入理解計算機系統 中鏈結這一章中,資料講乙個可執行檔案包含多個段。在linux系統中 段總是從0x08048000處開始,資料段在接下來的4kb對齊的位址處,執行時堆在接下來的讀寫段之後的第乙個4kb對齊的位址處,並通過呼叫malloc庫網上增長,開始於位址0x40000000處的段是為共...