之前在下曾使用 Arduino Nano 搭配 Python 模擬HID 功能,讓不支援 硬件HID 的裝置也能達到相同的效果。
由於 模擬HID 具備更高的操作權限,因此,在下想再次探索功能可行性。
不論是 硬件HID 還是 模擬HID ,即便功能再豐富、能自動執行大量指令,依然只能執行固定操作。
雖然在多數情況下這不成問題,但在特定情境下就會產生限制。
例如在 桌面Linux 環境中,一般的 複製快捷鍵 是 Ctrl+C;
但在 終端機 中, Ctrl+C 是發送 中止訊號 (Terminate Signal) , Ctrl+Shift+C 才是複製。
如果只維持固定的按鈕輸出,複製功能便無法在終端機中正常運作。
當然也有折衷的替代方案,例如專為 終端機 另外設定一顆 Ctrl+Shift+C 的按鈕。
然而,當同一項操作在不同軟件間都有不同的快捷鍵時,就必須為每個軟件設定專用按鍵。
在硬件按鍵數量有限的情況下,這樣做極為浪費資源。
因此在下認為,如果能夠在背景程式中自動判斷當前焦點的軟件,
就能實現 單一按鈕根據不同軟件,自動匹配相應快捷鍵 的效果。
製作 Sketch
在下使用先前撰寫的 Arduino Sketch 測試。
#define PIN 2
bool previous_status = false;
void setup() {
Serial.begin(115200);
pinMode(PIN, INPUT_PULLUP);
}
void loop() {
bool current_status = !digitalRead(PIN);
if (previous_status && !current_status) { // release
previous_status = !previous_status;
Serial.write(0x00);
} else if (!previous_status && current_status) { // press
previous_status = !previous_status;
Serial.write(0x01);
}
delay(1);
}
這是一個非常簡單的 Arduino Sketch:
當 第2支數碼腳位 偵測到 下降沿 (Falling Edge) 時,會向主機發送 0x01;
當 偵測到 上升沿 (Rising Edge) 時,則發送 0x00。
完成微控制器的訊號輸出設定後,便需要撰寫 宿主 (Host) 用於接收與處理訊號的程式。
製作背景程式
import time
from Xlib import X
from Xlib.display import Display
from serial import Serial
def get_active_window_class():
try:
return Display().create_resource_object(
"window", # 必須全小細
Display().screen().root.get_full_property(
Display().intern_atom("_NET_ACTIVE_WINDOW"), # 必須全大細
X.AnyPropertyType
).value[0]
).get_wm_class()
except:
return None
serial = Serial("/dev/ttyUSB0", 115200)
while True:
if serial.in_waiting >= 1:
raw_data = serial.read(1)
if raw_data == b"\x01":
window_class = get_active_window_class()
if window_class == None:
print("No Window info")
else:
if "mate-terminal" in window_class:
print("偵測到 {} ,Terminal 程式".format(window_class))
else:
print("偵測到 {} ,一般 GUI 程式".format(window_class))
time.sleep(0.001)
正常情況下,與軟件互動是依據 當前視窗焦點 來判斷。
因此,背景程式同樣可以將當前的視窗焦點作為判斷基準。
安裝Xlib
既然需要判斷視窗焦點,在 桌面Linux 中便可使用 Xlib函式庫 。
它除了是 X11 的基礎圖形介面函式庫外,在 Wayland 環境下也能透過相容層提供支援,例如 XWayland 。
在 終端機 輸入:
sudo apt install python-xlib
或 按此安裝 python-xlib 。
或使用 pip 安裝,輸入:
pip install xlib
安裝 Xlib函式庫 後即可執行上述 Python腳本 。
測試效果
連接 微控制器 並啟動 背景程式 後,只要 視窗焦點 切換並觸發 微控制器訊號,
背景程式 便能即時 擷取該視窗的 類別名稱 (Class Name) 或 軟體名稱,甚至連桌面環境都能區分。
import time, pyautogui
from Xlib import X
from Xlib.display import Display
from serial import Serial
def get_active_window_class():
try:
return Display().create_resource_object(
"window", # 必須全小細
Display().screen().root.get_full_property(
Display().intern_atom("_NET_ACTIVE_WINDOW"), # 必須全大細
X.AnyPropertyType
).value[0]
).get_wm_class()
except:
return None
serial = Serial("/dev/ttyUSB0", 115200)
while True:
if serial.in_waiting >= 1:
raw_data = serial.read(1)
if raw_data == b"\x01":
window_class = get_active_window_class()
if window_class == None:
print("No Window info")
else:
if "mate-terminal" in window_class:
print("偵測到 {} ,Terminal 程式".format(window_class))
pyautogui.hotkey("ctrl", "shift", "c")
else:
print("偵測到 {} ,一般 GUI 程式".format(window_class))
pyautogui.hotkey("ctrl", "c")
time.sleep(0.001)
確定能夠區分不同視窗或軟件後,便能修改 背景程式,根據當前焦點視窗執行對應的操作。
補充資料
不過需要注意的是, 桌面Linux 在 X11 與 Wayland 架構下,視窗管理機制 已經不同;
如果閣下是使用 Windows,可搭配 pywin32函式庫,而 Mac OS 則可使用 AppKit函式庫 來達成類似效果。
由於本文重點在於示範 Serial USB HID 背景程式如何實現跨視窗偵測,因此這裡就不逐一測試各個作業系統的具體實作,
在下只撰寫簡單腳本展示自動根據作業系統擷取視窗類別名稱。
import sys
def get_active_window_class():
try:
if sys.platform == "win32":
# Windows
# pip install pywin32
import win32gui
return win32gui.GetClassName(win32gui.GetForegroundWindow()
elif sys.platform == "darwin":
# Mac OS
# pip install pyobjc-framework-Cocoa
from AppKit import NSWorkspace
return NSWorkspace.sharedWorkspace().frontmostApplication().localizedName()
elif sys.platform.startswith("linux"):
# Linux
# pip install xlib
from Xlib import X
from Xlib.display import Display
return Display().create_resource_object(
"window",
Display().screen().root.get_full_property(
Display().intern_atom("_NET_ACTIVE_WINDOW"),
X.AnyPropertyType
).value[0]
).get_wm_class()
else:
return None
except:
return None
總結
雖然這次的內容屬於技術補充與測試驗證專案,但只要根據實際需求調整,
就能製作出高度客製化且靈活的專屬功能。
參考資料
文章經 AI 整理及潤飾。

沒有留言 :
張貼留言