社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

Python 如何快速制作 Windows 按键通知脚本

AirPython • 5 年前 • 970 次点击  



大家好,我是安果!

对于键盘没有背光灯的同学而言,切换大小写或控制 Num 键开关的时候没有提示,经常需要试探性地输入一些字符来判断开关是否打开,体验非常糟糕

因此,有人就想到自制脚本这一招,一旦触发大小写切换或 Num 键切换就进行 windows 通知提示:

https://github.com/skate1512/Toggle_Keys_Notification

今天我们来试试这个脚本,此外,我们还可以基于这个项目,扩展成任意一个按键被触发或切换都进行 windows 通知的脚本:


1.准备


请选择以下任一种方式输入命令安装依赖
1. Windows 环境 打开 Cmd (开始-运行-CMD )
2. MacOS 环境 打开 Terminal ( Command + 空格输入Terminal )
3. 如果你用的是 VSCode 编辑器 或 Pycharm,可以直接使用界面下方的Terminal

pip install win10toast


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 函数:

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)


效果如下:


当然,图标和标题还可以进一步优化:

比如:将 Lock Key State 这个标题用 toast_title 变量替代,默认为 Lock Key State。这样在调用 pop_up 函数的时候就能自定义标题了

最后效果图如下:


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/115216