对于键盘没有背光灯的同学而言,切换大小写或控制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
如果不能联通GitHub,或者网络速度比较慢,请在Python实用宝典公众号后台回复:按键触发通知 下载本文完整源代码。
2.源码使用与解析
2.1 源码使用
作者的项目可以在 Toggle_Keys_Notification 项目内,运行 notify.py 启动监听:
python notify.py
启动后点击一下大小写切换键,触发通知则说明代码正常运转:
2.1 源码分析
该项目通过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_LBUTTON | 01 | 1 | 鼠标的左键 |
VK_RBUTTON | 02 | 2 | 鼠标的右键 |
VK-CANCEL | 03 | 3 | Ctrl+Break(通常不需要处理) |
VK_MBUTTON | 04 | 4 | 鼠标的中键(三按键鼠标) |
VK_BACK | 08 | 8 | Backspace键 |
VK_TAB | 09 | 9 | Tab键 |
VK_CLEAR | 0C | 12 | Clear键(Num Lock关闭时的数字键盘5) |
VK_RETURN | 0D | 13 | Enter键 |
VK_SHIFT | 10 | 16 | Shift键 |
VK_CONTROL | 11 | 17 | Ctrl键 |
VK_MENU | 12 | 18 | Alt键 |
VK_PAUSE | 13 | 19 | Pause键 |
VK_CAPITAL | 14 | 20 | Caps Lock键 |
VK_ESCAPE | 1B | 27 | Ese键 |
VK_SPACE | 20 | 32 | Spacebar键 |
VK_PRIOR | 21 | 33 | Page Up键 |
VK_NEXT | 22 | 34 | Page Domw键 |
VK_END | 23 | 35 | End键 |
VK_HOME | 24 | 36 | Home键 |
VK_LEFT | 25 | 37 | LEFT ARROW 键(←) |
VK_UP | 26 | 38 | UP ARROW键(↑) |
VK_RIGHT | 27 | 39 | RIGHT ARROW键(→) |
VK_DOWN | 28 | 40 | DOWN ARROW键(↓) |
VK_Select | 29 | 41 | Select键 |
VK_PRINT | 2A | 42 | Print键 |
VK_EXECUTE | 2B | 43 | EXECUTE键 |
VK_SNAPSHOT | 2C | 44 | Print Screen键(抓屏) |
VK_Insert | 2D | 45 | Ins键(Num Lock关闭时的数字键盘0) |
VK_Delete | 2E | 46 | Del键(Num Lock关闭时的数字键盘.) |
VK_HELP | 2F | 47 | Help键 |
VK_0 | 30 | 48 | 0键 |
VK_1 | 31 | 49 | 1键 |
VK_2 | 32 | 50 | 2键 |
VK_3 | 33 | 51 | 3键 |
VK_4 | 34 | 52 | 4键 |
VK_5 | 35 | 53 | 5键 |
VK_6 | 36 | 54 | 6键 |
VK_7 | 37 | 55 | 7键 |
VK_8 | 38 | 56 | 8键 |
VK_9 | 39 | 57 | 9键 |
VK_A | 41 | 65 | A键 |
VK_B | 42 | 66 | B键 |
VK_C | 43 | 67 | C键 |
VK_D | 44 | 68 | D键 |
VK_E | 45 | 69 | E键 |
VK_F | 46 | 70 | F键 |
VK_G | 47 | 71 | G键 |
VK_H | 48 | 72 | H键 |
VK_I | 49 | 73 | I键 |
VK_J | 4A | 74 | J键 |
VK_K | 4B | 75 | K键 |
VK_L | 4C | 76 | L键 |
VK_M | 4D | 77 | M键 |
VK_N | 4E | 78 | N键 |
VK_O | 4F | 79 | O键 |
VK_P | 50 | 80 | P键 |
VK_Q | 51 | 81 | Q键 |
VK_R | 52 | 82 | R键 |
VK_S | 53 | 83 | S键 |
VK_T | 54 | 84 | T键 |
VK_U | 55 | 85 | U键 |
VK_V | 56 | 86 | V键 |
VK_W | 57 | 87 | W键 |
VK_X | 58 | 88 | X键 |
VK_Y | 59 | 89 | Y键 |
VK_Z | 5A | 90 | Z键 |
VK_NUMPAD0 | 60 | 96 | 数字键0键 |
VK_NUMPAD1 | 61 | 97 | 数字键1键 |
VK_NUMPAD2 | 62 | 98 | 数字键2键 |
VK_NUMPAD3 | 62 | 99 | 数字键3键 |
VK_NUMPAD4 | 64 | 100 | 数字键4键 |
VK_NUMPAD5 | 65 | 101 | 数字键5键 |
VK_NUMPAD6 | 66 | 102 | 数字键6键 |
VK_NUMPAD7 | 67 | 103 | 数字键7键 |
VK_NUMPAD8 | 68 | 104 | 数字键8键 |
VK_NUMPAD9 | 69 | 105 | 数字键9键 |
VK_MULTIPLY | 6A | 106 | 数字键盘上的*键 |
VK_ADD | 6B | 107 | 数字键盘上的+键 |
VK_SEPARATOR | 6C | 108 | Separator键 |
VK_SUBTRACT | 6D | 109 | 数字键盘上的-键 |
VK_DECIMAL | 6E | 110 | 数字键盘上的.键 |
VK_DIVIDE | 6F | 111 | 数字键盘上的/键 |
VK_F1 | 70 | 112 | F1键 |
VK_F2 | 71 | 113 | F2键 |
VK_F3 | 72 | 114 | F3键 |
VK_F4 | 73 | 115 | F4键 |
VK_F5 | 74 | 116 | F5键 |
VK_F6 | 75 | 117 | F6键 |
VK_F7 | 76 | 118 | F7键 |
VK_F8 | 77 | 119 | F8键 |
VK_F9 | 78 | 120 | F9键 |
VK_F10 | 79 | 121 | F10键 |
VK_F11 | 7A | 122 | F11键 |
VK_F12 | 7B | 123 | F12键 |
VK_NUMLOCK | 90 | 144 | Num Lock 键 |
VK_SCROLL | 91 | 145 | Scroll 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 函数:
def 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)
效果如下:
当然,图标和标题还可以进一步优化:
def pop_up(body, icon, toast_title="Lock Key State"): """Generates Pop-up notification when state changes""" notification = ToastNotifier() notification.show_toast(toast_title, body, icon_path="assets\\"+icon, duration=1.5)
比如将Lock Key State这个标题用 toast_title 变量替代,默认为Lock Key State。这样在调用pop_up函数的时候就能自定义标题了,效果如下:
总而言之,能扩展的东西非常多,这只是一个学习的例子,如果大家感兴趣的话可以在 Python实用宝典 公众号后台回复 按键触发通知 下载完整源代码进行改造。
我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。
有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。
原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!
Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典