这段时间一直在忙活给FeelUOwn补充一下实用的细节功能。其中全局快捷键就是我下手的第一个目标。而对于一个音乐播放器,笔记本键盘的MultiMedia快捷键肯定要捕捉到吧。然后我自己使用的mac,所以我想让它也支持mac平台。
经过一段时间的google,我尝试了几种解决方案
-
pygs
,pyglobalshortcut
, 和qt绑定, 依赖libqxt这个库,你搜python,global shortcut 这个东西基本上是排在第一。但是我在mac上, pip安装竟然失败失败,这是放弃的一个原因。另外,我想这种方案应该是无法捕获到Mac上的multimedia快捷键(没有验证过)。 -
稍微进入底层,尝试用更加 lower level 的解决方案。尝试在各平台使用不同的方法。Linux平台使用Python3-xlib, mac平台使用 python-Objc,Quartz(基本可以理解为mac平台原生GUI库的python绑定
我觉得第二种方案应该是比较合理的。
-
一方面,想使用一个库来达到跨平台的效果基本不现实,没有发现类似的解决方案。从而,各平台采用不同的方法就是一个比较直接的想法。
-
另外一方面,我在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))