1. 程式人生 > 其它 >使用Python來製作一個按鍵觸發Windows通知的指令碼

使用Python來製作一個按鍵觸發Windows通知的指令碼

技術標籤:python

對於鍵盤沒有背光燈的同學而言,切換大小寫或控制Num鍵開關的時候沒有提示,經常需要試探性地輸入一些字元來判斷開關是否開啟,體驗非常糟糕。

因此,有人就想到自制指令碼這一招,一旦觸發大小寫切換或Num鍵切換就進行windows通知提示:

https://github.com/skate1512/Toggle_Keys_Notification

今天我們來試試這個指令碼,此外,我們還可以基於這個專案,擴充套件成任意一個按鍵被觸發或切換都進行 windows 通知的指令碼:

1.準備

開始之前,你要確保Python和pip已經成功安裝在電腦上,如果沒有,請訪問這篇文章:超詳細Python安裝指南

進行安裝。如果你用Python的目的是資料分析,可以直接安裝Anaconda:Python資料分析與挖掘好幫手—Anaconda,它內建了Python和pip.

此外,推薦大家用VSCode編輯器,因為它可以在編輯器下方的終端執行命令安裝依賴模組:Python 程式設計的最好搭檔—VSCode 詳細指南

Windows環境下開啟 Cmd (開始-執行-CMD),蘋果系統環境下請開啟 Terminal (command+空格輸入Terminal),輸入命令安裝依賴:

pip install win10toast

除此之外,我們需要下載作者的程式碼,GitHub地址:
https://github.com/skate1512/Toggle_Keys_Notification

2.原始碼使用與解析

2.1 原始碼使用

作者的專案可以在 Toggle_Keys_Notification 專案內,執行 notify.py 啟動監聽:

python notify.py

啟動後點擊一下大小寫切換鍵,觸發通知則說明程式碼正常運轉:

2.2 原始碼分析

該專案通過win32gui和win32con實現了彈出toast進行通知的功能,最核心的_show_toast程式碼位於 toast.py 中,下面是這個函式的部分程式碼剖析:

註冊和建立 window :

message_map = {WM_DESTROY: self.on_destroy, }

註冊Window

self.wc = WNDCLASS()

self.hinst = self.wc.hInstance = GetModuleHandle(None)
self.wc.lpszClassName = str("PythonTaskbar") # 定義該視窗結構的名稱
self.wc.lpfnWndProc = message_map
try:
self.classAtom = RegisterClass(self.wc)
except:
pass

Window格式

style = WS_OVERLAPPED | WS_SYSMENU

建立Window

self.hwnd = CreateWindow(self.classAtom, "Taskbar", style,
0, 0, CW_USEDEFAULT,
CW_USEDEFAULT,
0, 0, self.hinst, None)
UpdateWindow(self.hwnd)

所使用到的win32模組解析如下。

GetModuleHandle: 獲取一個應用程式或動態連結庫的模組控制代碼。
WM_DESTROY: 關閉程式。
RegisterClass: 將定義好的Window屬性儲存儲存下來。
WS_OVERLAPPED: 重疊式視窗,該式樣視窗 帶有一個標題欄和邊框。
WS_SYSMENU: 具有 SYSTEM 選單欄的樣式
CW_USEDEFAULT: 採用系統預設位置

CreateWindow 這個函式具有非常多的引數,甚至有一個百度百科來詳細解析每一個引數的具體作用,大家感興趣可以移步:
https://baike.baidu.com/item/CreateWindow/5076220

瞭解win32這些模組名稱的意義後,理解上述程式碼的邏輯便很輕鬆了。

圖示載入及工作列圖示顯示配置:

圖示

if icon_path is not None:
# 獲取圖示地址
icon_path = path.realpath(icon_path)
else:
icon_path = resource_filename(Requirement.parse("win10toast"), "win10toast/data/python.ico")

載入格式

icon_flags = LR_LOADFROMFILE | LR_DEFAULTSIZE
try:
hicon = LoadImage(self.hinst, icon_path, IMAGE_ICON, 0, 0, icon_flags)
except Exception as e:
logging.error("Some trouble with the icon ({}): {}"
.format(icon_path, e))
hicon = LoadIcon(0, IDI_APPLICATION)

工作列圖示

flags = NIF_ICON | NIF_MESSAGE | NIF_TIP
nid = (self.hwnd, 0, flags, WM_USER + 20, hicon, "Tooltip")
Shell_NotifyIcon(NIM_ADD, nid)
Shell_NotifyIcon(NIM_MODIFY, (self.hwnd, 0, NIF_INFO, WM_USER + 20, hicon, "Balloon Tooltip", msg, 200, title, NIIF_ICON_MASK))

等待一會後銷燬

sleep(duration)
DestroyWindow(self.hwnd)
UnregisterClass(self.wc.lpszClassName, None)

這部分程式碼控制了通知彈出框的展示和銷燬。如果你希望通知彈出框久一點再消失,可以適當修改傳入的 duration 變數值。

DestroyWindow後,通知彈出框便消失了,整個 show_toast 的過程結束。

其實非常簡單,從 CreateWindow 到 DestroyWindow 處理彈出框的各種屬性,然後登出窗體,完成整個彈出流程。

3.擴充套件觸發通知

為了擴充套件監聽的按鍵,並能監聽按鍵觸發,需要先了解 notify.py 是如何檢測到按鍵變化的。

獲取按鍵狀態:

keyboard = ctypes.WinDLL("User32.dll")
VK_NUMLOCK = 0x90
VK_CAPITAL = 0x14
def get_capslock_state():
"""Returns the current Caps Lock State(On/Off)"""
return "Caps Lock On" if keyboard.GetKeyState(VK_CAPITAL) else "Caps Lock Off"

def get_numlock_state():
"""Returns The current Num Lock State(On/Off)"""
return "Num Lock On" if keyboard.GetKeyState(VK_NUMLOCK) else "Num Lock Off"

可以看到,獲取按鍵狀態是通過 keyboard.GetKeyState(XXXX) 實現的。

而這個XXXX是對應的按鍵的十六進位制,比如 VK_NUMLOCK 是Num鍵,對應的16進位制程式碼是0x90,VK_CAPITAL 是大小寫按鍵,對應的十六進位制程式碼是0x14.

變數名是可以使用者自定義的,比如大小寫鍵有些人習慣稱之為VK_CAPITAL,也有些人喜歡稱之為 VK_CAPITAL,都可以,只要其最終對應的變數值為十六進位制的0x14即可。

部分按鍵16進位制清單如下(完整版可以閱讀原文檢視):

常數名稱十六進位制值對應按鍵
VK_BACK08Backspace鍵
VK_TAB09Tab鍵
VK_CLEAR0CClear鍵(Num Lock關閉時的數字鍵盤5)
VK_RETURN0DEnter鍵
VK_SHIFT10Shift鍵
VK_CONTROL11Ctrl鍵
VK_MENU12Alt鍵
VK_PAUSE13Pause鍵
VK_CAPITAL14Caps Lock鍵

再來看看監聽邏輯:

caps_curr = get_capslock_state()
num_curr = get_numlock_state()

while True:
caps_change = get_capslock_state()
num_change = get_numlock_state()

if caps_curr != caps_change:
    if caps_change == "Caps Lock On":
        pop_up("Caps Lock On", "CapsLock_On.ico")
    else:
        pop_up("Caps Lock Off", "CapsLock_Off.ico")
    caps_curr = caps_change
    time.sleep(0.1)

if num_curr != num_change:
    if num_change == "Num Lock On":
        pop_up("Num Lock On", "NumLock_On.ico")
    else:
        pop_up("Num Lock Off", "NumLock_Off.ico")
    num_curr = num_change
time.sleep(0.2)

在剛開始執行監聽指令碼時,先獲取到按鍵的狀態,在迴圈體中,不斷地獲得當前按鍵狀態,如果發生了狀態變化,則觸發pop_up函式,彈出剛剛我們提到的show_toast 函式:

ef pop_up(body, icon):
"""Generates Pop-up notification when state changes"""
notification = ToastNotifier()
notification.show_toast("Lock Key State", body, icon_path="assets\"+icon, duration=1.5)

整套監聽並通知的機制還是非常簡單的,如果我們想要自定義一些按鍵,你只需要在開頭新增對應的按鍵的十六進位制編碼,然後新增一些監聽函式。

比如我們想監聽 ESC 按鍵被按下:VK_ESCAPE=0x1B,使用 keyboard 模組新增一個鉤子函式,監聽按鍵:

import keyboard as kb
def hook_esc(button):
"""Alert if ESC button is pressed"""
esc_button = kb.KeyboardEvent('down', VK_ESCAPE, 'ESC')
if button.event_type == 'down' and esc_button.name == button.name:
pop_up("ESC Pressed", "CapsLock_On.ico")
# 敲擊後回填為None
button.event_type = None

然後再在迴圈體內新增判斷邏輯:

kb.hook(hook_esc)

效果如下:

當然,圖示和標題還可以進一步優化:

比如將Lock Key State這個標題用 toast_title 變數替代,預設為Lock Key State。這樣在呼叫pop_up函式的時候就能自定義標題了,效果如下:

總而言之,能擴充套件的東西非常多,這只是一個學習的例子,如果大家感興趣的話可以完整原始碼進行改造。

GitHub地址:
https://github.com/skate1512/Toggle_Keys_Notification