python - 跨平台全局快捷键解决方案

原创
2015/08/25 16:23
阅读数 5.4K

原文地址: http://www.cosven.com/2015/08/24/%E5%85%A8%E5%B1%80%E5%BF%AB%E6%8D%B7%E9%94%AE%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/

这段时间一直在忙活给FeelUOwn补充一下实用的细节功能。其中全局快捷键就是我下手的第一个目标。而对于一个音乐播放器,笔记本键盘的MultiMedia快捷键肯定要捕捉到吧。然后我自己使用的mac,所以我想让它也支持mac平台。

经过一段时间的google,我尝试了几种解决方案

  1. pygs, pyglobalshortcut, 和qt绑定, 依赖libqxt这个库,你搜python,global shortcut 这个东西基本上是排在第一。但是我在mac上, pip安装竟然失败失败,这是放弃的一个原因。另外,我想这种方案应该是无法捕获到Mac上的multimedia快捷键(没有验证过)。

  2. 稍微进入底层,尝试用更加 lower level 的解决方案。尝试在各平台使用不同的方法。Linux平台使用Python3-xlib, mac平台使用 python-Objc,Quartz(基本可以理解为mac平台原生GUI库的python绑定

我觉得第二种方案应该是比较合理的。

  1. 一方面,想使用一个库来达到跨平台的效果基本不现实,没有发现类似的解决方案。从而,各平台采用不同的方法就是一个比较直接的想法。

  2. 另外一方面,我在Github上看到一个 PyUserInput,它是一个用来模拟鼠标点击、键盘按压的跨平台的库。我看它的依赖:

     Depending on your platform, you will need the following python modules for PyUserInput to function:
         Linux - Xlib
         Mac - Quartz, AppKit
         Windows - pywin32, pyHook
    

我之前在windows上尝试全局快捷键的时候就是用的pywin32,所以类比推理一下,第二种方案是比较合理的-跨3大平台的-python全局快捷键解决方案。

这些方法的一个共通的思想就是 监听系统特定的事件

** mac 平台**

# -*- coding: utf8 -*-

# 主要参考: https://gist.github.com/nevyn/764542/73e53a77dc59d7c78510069d9215f4a0a17c6cb8
# mac 平台

import Quartz
from AppKit import NSKeyUp, NSSystemDefined, NSEvent, NSKeyDownMask


def keyboardTapCallback(proxy, type_, event, refcon):
    keyEvent = NSEvent.eventWithCGEvent_(event)
    if (keyEvent.subtype() == 8):
        key_code = (keyEvent.data1() & 0xFFFF0000) >> 16
        key_state = (keyEvent.data1() & 0xFF00) >> 8
        if key_code is 16 or key_code is 19 or key_code is 20:
            # 16 for play-pause, 19 for next, 20 for previous
            return None  # 截取到事件,其他程序将不会收到该事件。如果之前是listen only就不能截取
    return event

# Set up a tap, with type of tap, location, options and event mask
tap = Quartz.CGEventTapCreate(
    Quartz.kCGSessionEventTap, # Session level is enough for our needs
    Quartz.kCGHeadInsertEventTap, # Insert wherever, we do not filter
    Quartz.kCGEventTapOptionListenOnly, # (如果是default就可以截取事件, 现在只能监听)
    Quartz.CGEventMaskBit(NSSystemDefined), # NSSystemDefined for media keys
    keyboardTapCallback,
    None
)


runLoopSource = Quartz.CFMachPortCreateRunLoopSource(None, tap, 0)
Quartz.CFRunLoopAddSource(
    Quartz.CFRunLoopGetCurrent(),
    runLoopSource,
    Quartz.kCFRunLoopDefaultMode
)
# Enable the tap
Quartz.CGEventTapEnable(tap, True)
# and run! This won't return until we exit or are terminated.
Quartz.CFRunLoopRun()

** Linux 环境 **

# -*- coding: utf8 -*-
from Xlib.display import Display
from Xlib import X
from Xlib.ext import record
from Xlib.protocol import rq

disp = None

def handler(reply):
    """ This function is called when a xlib event is fired """
    data = reply.data
    while len(data):
        event, data = rq.EventField(None).parse_binary_value(data, disp.display, None, None)

        # KEYCODE IS FOUND USERING event.detail
        print(event.detail)

        if event.type == X.KeyPress:
            # BUTTON PRESSED
            print("pressed")
        elif event.type == X.KeyRelease:
            # BUTTON RELEASED
            print("released")

# get current display
disp = Display()
root = disp.screen().root

# Monitor keypress and button press
ctx = disp.record_create_context(
            0,
            [record.AllClients],
            [{
                    'core_requests': (0, 0),
                    'core_replies': (0, 0),
                    'ext_requests': (0, 0, 0, 0),
                    'ext_replies': (0, 0, 0, 0),
                    'delivered_events': (0, 0),
                    'device_events': (X.KeyReleaseMask, X.ButtonReleaseMask),
                    'errors': (0, 0),
                    'client_started': False,
                    'client_died': False,
            }])
disp.record_enable_context(ctx, handler)
disp.record_free_context(ctx)

while 1:
    # Infinite wait, doesn't do anything as no events are grabbed
    event = root.display.next_event()

** windows平台 **

# -*- coding: utf8-*-
# windows 平台
# 这段代码很久之前测试过,或许会有些许bug

import sys
import time
from ctypes import *
from ctypes.wintypes import *

delta = 0.3
lastTime = 0

WM_HOTKEY   = 0x0312
MOD_ALT     = 0x0001
MOD_CONTROL = 0x0002
MOD_SHIFT   = 0x0004
WM_KEYUP    = 0x0101
class MSG(Structure):
    _fields_ = [('hwnd', c_int),
                ('message', c_uint),
                ('wParam', c_int),
                ('lParam', c_int),
                ('time', c_int),
                ('pt', POINT)]
key = 192 # ~ 键
hotkeyId = 1
if not windll.user32.RegisterHotKey(None, hotkeyId, None, key):
    sys.exit("Cant Register Hotkey")

msg = MSG()
while True:
    if (windll.user32.GetMessageA(byref(msg), None, 0, 0) != 0):
        if msg.message == WM_HOTKEY and msg.wParam == hotkeyId:
            if (time.time() - lastTime) < delta:
                print "down"
            else:
                pass
            lastTime = time.time()
        if msg.message == WM_KEYUP:
            print "up"
        windll.user32.TranslateMessage(byref(msg))
        windll.user32.DispatchMessageA(byref(msg))


展开阅读全文
打赏
0
11 收藏
分享
加载中
更多评论
打赏
0 评论
11 收藏
0
分享
返回顶部
顶部