在Linux上使用Python监听全局键组合
我刚写了一个小程序,可以每隔几分钟从Flickr上下载一张新的壁纸。
现在我想增加一个功能,让用户可以“喜欢”一张壁纸,这样喜欢的壁纸就会比不喜欢的壁纸出现得更频繁。
我想给这个功能设置一个全局的快捷键。
比如说:如果我按下ctrl+7,就能在Python中执行一个“喜欢”的功能。
有没有什么库可以实现这个功能?(比如在JavaScript中,有一个库可以用shortcut("ctrl-b", someFunction);
来定义快捷键)
如果没有,那我该怎么做呢?我看到过这个类似的问题,但那是很久以前的了。
3 个回答
这里有一个更简洁的版本,它不仅解码了数据,还打印出了键的名称,而不仅仅是数字:
#!/usr/bin/env python2
# Captures all keyboard and mouse events, including modifiers
# Adapted from http://stackoverflow.com/questions/22367358/
# Requires python-xlib
from Xlib.display import Display
from Xlib import X, XK
from Xlib.ext import record
from Xlib.protocol import rq
class Listener:
def __init__(self):
self.disp = None
self.keys_down = set()
def keycode_to_key(self, keycode, state):
i = 0
if state & X.ShiftMask:
i += 1
if state & X.Mod1Mask:
i += 2
return self.disp.keycode_to_keysym(keycode, i)
def key_to_string(self, key):
keys = []
for name in dir(XK):
if name.startswith("XK_") and getattr(XK, name) == key:
keys.append(name.lstrip("XK_").replace("_L", "").replace("_R", ""))
if keys:
return " or ".join(keys)
return "[%d]" % key
def keycode_to_string(self, keycode, state):
return self.key_to_string(self.keycode_to_key(keycode, state))
def mouse_to_string(self, code):
if code == X.Button1:
return "Button1"
elif code == X.Button2:
return "Button2"
elif code == X.Button3:
return "Button3"
elif code == X.Button4:
return "Button4"
elif code == X.Button5:
return "Button5"
else:
return "{%d}" % code
def down(self, key):
self.keys_down.add(key)
self.print_keys()
def up(self, key):
if key in self.keys_down:
self.keys_down.remove(key)
self.print_keys()
def print_keys(self):
keys = list(self.keys_down)
print "Currently pressed:", ", ".join(keys)
def event_handler(self, reply):
data = reply.data
while data:
event, data = rq.EventField(None).parse_binary_value(data, self.disp.display, None, None)
if event.type == X.KeyPress:
self.down(self.keycode_to_string(event.detail, event.state))
elif event.type == X.KeyRelease:
self.up(self.keycode_to_string(event.detail, event.state))
elif event.type == X.ButtonPress:
self.down(self.mouse_to_string(event.detail))
elif event.type == X.ButtonRelease:
self.up(self.mouse_to_string(event.detail))
def run(self):
self.disp = Display()
XK.load_keysym_group('xf86')
root = self.disp.screen().root
ctx = self.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,
}])
self.disp.record_enable_context(ctx, lambda reply: self.event_handler(reply))
self.disp.record_free_context(ctx)
while True:
event = root.display.next_event()
if __name__ == "__main__":
Listener().run()
输出结果看起来是这样的:
Currently pressed: Alt
Currently pressed:
Currently pressed: Alt
Currently pressed: Alt, Tab
Currently pressed: Alt
Currently pressed: Alt, Tab
Currently pressed: Alt
Currently pressed: Alt, Tab
Currently pressed: Alt
Currently pressed: Alt, Tab
Currently pressed: Alt
Currently pressed: Alt, Tab
Currently pressed: Alt
Currently pressed:
Currently pressed: Control
Currently pressed: Control, a
Currently pressed: Control
Currently pressed: Control, Shift
Currently pressed: Control, Shift, A
Currently pressed: Control, Shift
对于想看看我最终使用的代码的人,我在这里做了一个小的代码片段 就在这里。
每台电脑的按键代码可能会有些不同。另外,我快速写的这个按键监听类并没有考虑到所有可能出现的问题。不过,对我来说,它的效果还不错。
我不知道有没有专门设计用来扩展的库。不过,正如你链接中提到的,pykeylogger的后台给出了一个如何实现的例子,但看起来有点复杂,不太适合你的需求。
pykeylogger使用python-xlib模块来捕捉X显示器上的按键。有人已经在pastebin上创建了一个更简单的例子,下面是从那里复制过来的源代码。
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()
你需要扩展处理程序,以满足你的需求,不仅仅是打印到屏幕上,然后把它放到一个单独的线程中。
另一个(比较麻烦的)选择是直接监听键盘,而不依赖外部库或X会话。在Linux中,一切都是文件,你的键盘输入会在/dev/input中,你可以把它当作文件来读取,比如open('/dev/input/event2', 'rb')
,在后台进行。这种方法不太推荐,因为它需要更高的权限,还要搞清楚哪个设备是键盘,然后自己创建按键映射。我只是想让你知道如果有必要的话,这种方法是可行的。
补充:我还发现了使用Python gtk3在X上实现全局按键绑定,看起来有更多的例子可以参考。